Pierwsze kroki z wektorami dystrybucyjnymi w Cloud SQL for PostgreSQL

1. Wprowadzenie

W tym laboratorium z kodami dowiesz się, jak używać Cloud SQL do integracji z Vertex AI, łącząc wyszukiwanie wektorowe z wektorami dystrybucyjnymi Vertex AI.

30b7c4dcdd8bb68f.png

Wymagania wstępne

  • podstawowa znajomość konsoli Google Cloud;
  • podstawowe umiejętności w zakresie interfejsu wiersza poleceń i Cloud Shell;

Czego się nauczysz

  • Wdrażanie instancji Cloud SQL for PostgreSQL
  • Jak utworzyć bazę danych i włączyć integrację z Cloud SQL AI
  • Wczytywanie danych do bazy danych
  • Jak używać modelu wektorów dystrybucyjnych Vertex AI w Cloud SQL
  • Jak wzbogacić wynik za pomocą modelu generatywnego Vertex AI
  • Jak zwiększyć wydajność dzięki indeksowi wektorowemu

Czego potrzebujesz

  • Konto Google Cloud i projekt Google Cloud
  • przeglądarka internetowa, np. Chrome, obsługująca konsolę Google Cloud i Cloud Shell;

2. Konfiguracja i wymagania

Konfiguracja środowiska w samodzielnym tempie

  1. Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub użyj istniejącego. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.

fbef9caa1602edd0.png

a99b7ace416376c4.png

5e3ff691252acf41.png

  • Nazwa projektu to wyświetlana nazwa uczestników tego projektu. Jest to ciąg znaków, którego nie używają interfejsy API Google. Zawsze możesz ją zaktualizować.
  • Identyfikator projektu jest niepowtarzalny w ramach wszystkich projektów Google Cloud i nie można go zmienić (po ustawieniu). Konsola Cloud automatycznie generuje unikalny ciąg znaków. Zwykle nie ma znaczenia, jaki to ciąg. W większości laboratoriów z kodem trzeba podać identyfikator projektu (zwykle oznaczony jako PROJECT_ID). Jeśli nie podoba Ci się wygenerowany identyfikator, możesz wygenerować inny losowy. Możesz też spróbować użyć własnego adresu e-mail, aby sprawdzić, czy jest on dostępny. Po wykonaniu tego kroku nie można go zmienić. Pozostanie on na stałe w ramach projektu.
  • Informacyjnie: istnieje jeszcze 3 wartość, numer projektu, której używają niektóre interfejsy API. Więcej informacji o wszystkich 3 wartościach znajdziesz w dokumentacji.
  1. Następnie musisz włączyć rozliczenia w konsoli Cloud, aby korzystać z zasobów i interfejsów API Cloud. Przejście przez ten samouczek nie będzie kosztowne, a być może nawet bezpłatne. Aby wyłączyć zasoby i uniknąć obciążenia opłatami po zakończeniu samouczka, możesz usunąć utworzone zasoby lub usunąć projekt. Nowi użytkownicy Google Cloud mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.

Uruchomienie Cloud Shell

Google Cloud można obsługiwać zdalnie z laptopa, ale w tym ćwiczeniu będziesz korzystać z Google Cloud Shell, czyli środowiska wiersza poleceń działającego w chmurze.

W konsoli Google Cloud kliknij ikonę Cloud Shell na pasku narzędzi w prawym górnym rogu:

55efc1aaa7a4d3ad.png

Uzyskanie dostępu do środowiska i połączenie się z nim powinno zająć tylko kilka chwil. Po jego zakończeniu powinno wyświetlić się coś takiego:

7ffe5cbb04455448.png

Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie poprawia wydajność sieci i uwierzytelnianie. Wszystkie zadania w tym CodeLab możesz wykonać w przeglądarce. Nie musisz niczego instalować.

3. Zanim zaczniesz

Włącz interfejs API

Dane wyjściowe:

W Cloud Shell sprawdź, czy identyfikator projektu jest skonfigurowany:

gcloud config set project [YOUR-PROJECT-ID]

Ustaw zmienną środowiskową PROJECT_ID:

PROJECT_ID=$(gcloud config get-value project)

Włącz wszystkie niezbędne usługi:

gcloud services enable sqladmin.googleapis.com \
                       compute.googleapis.com \
                       cloudresourcemanager.googleapis.com \
                       servicenetworking.googleapis.com \
                       aiplatform.googleapis.com

Oczekiwany wynik

student@cloudshell:~ (test-project-001-402417)$ gcloud config set project test-project-001-402417
Updated property [core/project].
student@cloudshell:~ (test-project-001-402417)$ PROJECT_ID=$(gcloud config get-value project)
Your active configuration is: [cloudshell-14650]
student@cloudshell:~ (test-project-001-402417)$ 
student@cloudshell:~ (test-project-001-402417)$ gcloud services enable sqladmin.googleapis.com \
                       compute.googleapis.com \
                       cloudresourcemanager.googleapis.com \
                       servicenetworking.googleapis.com \
                       aiplatform.googleapis.com
Operation "operations/acat.p2-4470404856-1f44ebd8-894e-4356-bea7-b84165a57442" finished successfully.

4. Tworzenie instancji Cloud SQL

Utwórz instancję Cloud SQL z integracją bazy danych z Vertex AI.

Tworzenie hasła do bazy danych

Określ hasło domyślnego użytkownika bazy danych. Możesz zdefiniować własne hasło lub wygenerować je za pomocą funkcji losowania:

export CLOUDSQL_PASSWORD=`openssl rand -hex 12`

Zanotuj wygenerowaną wartość hasła:

echo $CLOUDSQL_PASSWORD

Tworzenie instancji Cloud SQL for PostgreSQL

W sesji Cloud Shell wykonaj te czynności:

gcloud sql instances create my-cloudsql-instance \
--database-version=POSTGRES_16 \
--tier=db-custom-1-3840 \
--region=us-central1 \
--edition=ENTERPRISE \
--enable-google-ml-integration \
--database-flags cloudsql.enable_google_ml_integration=on

Po utworzeniu instancji musimy ustawić hasło dla domyślnego użytkownika w instancji i sprawdzić, czy możemy się połączyć z hasłem.

gcloud sql users set-password postgres \
    --instance=my-cloudsql-instance \
    --password=$CLOUDSQL_PASSWORD

Uruchom polecenie i wpisz hasło w oknie dialogowym, gdy będzie gotowe do połączenia.

gcloud sql connect my-cloudsql-instance --user=postgres

Włącz integrację z Vertex AI

Przyznaj niezbędne uprawnienia wewnętrznemu kontu usługi cloud sql, aby móc korzystać z integracji Vertex AI.

Znajdź adres e-mail wewnętrznego konta usługi Cloud SQL i wyeksportuj go jako zmienną.

SERVICE_ACCOUNT_EMAIL=$(gcloud sql instances describe my-cloudsql-instance --format="value(serviceAccountEmailAddress)")
echo $SERVICE_ACCOUNT_EMAIL

Przyznaj dostęp do Vertex AI kontu usługi Cloud SQL:

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_EMAIL" \
  --role="roles/aiplatform.user"

Więcej informacji o tworzeniu i konfigurowaniu instancji znajdziesz tutaj w dokumentacji Cloud SQL.

5. Przygotuj bazę danych

Teraz musimy utworzyć bazę danych i włączyć obsługę wektorów.

Utwórz bazę danych

Utwórz bazę danych o nazwie quickstart_db. Aby to zrobić, możesz użyć różnych opcji, takich jak klienci baz danych na linii poleceń, np. psql dla PostgreSQL, SDK lub Cloud SQL Studio. Do tworzenia baz danych i łączenia się z instancją użyjemy pakietu SDK (gcloud).

W Cloud Shell wykonaj polecenie tworzenia bazy danych.

gcloud sql databases create quickstart_db --instance=my-cloudsql-instance

Włączanie rozszerzeń

Aby móc pracować z Vertex AI i wektorami, musimy włączyć 2 rozszerzenia w naszej utworzonej bazie danych.

W Cloud Shell uruchom polecenie, aby nawiązać połączenie z utworzoną bazą danych (musisz podać hasło).

gcloud sql connect my-cloudsql-instance --database quickstart_db --user=postgres

Następnie, po nawiązaniu połączenia, w sesji SQL musisz uruchomić 2 polecenia:

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

Zakończ sesję SQL:

exit;

6. Wczytaj dane

Teraz musimy utworzyć obiekty w bazie danych i wczytać dane. Użyjemy fikcyjnych danych ze sklepu Cymbal. Dane są dostępne w publicznym zasobniku Google Storage w formacie CSV.

Najpierw musimy utworzyć w bazie danych wszystkie wymagane obiekty. W tym celu użyjemy znanych już poleceń gcloud sql connect i gcloud storage, aby pobrać i zaimportować obiekty schematu do naszej bazy danych.

W Cloud Shell wykonaj polecenie i podaj hasło zanotowane podczas tworzenia instancji:

gcloud storage cat gs://cloud-training/gcc/gcc-tech-004/cymbal_demo_schema.sql |gcloud sql connect my-cloudsql-instance --database quickstart_db --user=postgres

Co dokładnie zrobiliśmy w poprzednim poleceniu? Połączyliśmy się z naszą bazą danych i wykonaliśmy pobrany kod SQL, który utworzył tabele, indeksy i sekwencje.

Następnym krokiem jest załadowanie danych. Aby to zrobić, musimy pobrać pliki CSV z Google Cloud Storage.

gcloud storage cp gs://cloud-training/gcc/gcc-tech-004/cymbal_products.csv .
gcloud storage cp gs://cloud-training/gcc/gcc-tech-004/cymbal_inventory.csv .
gcloud storage cp gs://cloud-training/gcc/gcc-tech-004/cymbal_stores.csv .

Następnie musimy połączyć się z bazą danych.

gcloud sql connect my-cloudsql-instance --database quickstart_db --user=postgres

Możesz też importować dane z plików CSV.

\copy cymbal_products from 'cymbal_products.csv' csv header
\copy cymbal_inventory from 'cymbal_inventory.csv' csv header
\copy cymbal_stores from 'cymbal_stores.csv' csv header

Zakończ sesję SQL:

exit;

Jeśli masz własne dane i pliki CSV są zgodne z narzędziem do importowania do Cloud SQL dostępnym w Cloud Console, możesz z niego skorzystać zamiast wiersza poleceń.

7. Tworzenie wektorów

Następnym krokiem jest utworzenie wektorów dystrybucyjnych dla opisów produktów za pomocą modelu textembedding-004 z Vertex AI od Google i zapisanie ich jako danych wektorowych.

Połącz się z bazą danych:

gcloud sql connect my-cloudsql-instance --database quickstart_db --user=postgres

Utwórz też kolumnę wirtualną embedding w tabeli cymbal_products za pomocą funkcji embedding.

ALTER TABLE cymbal_products ADD COLUMN embedding vector(768) GENERATED ALWAYS AS (embedding('text-embedding-004',product_description)) STORED;

Może to zająć trochę czasu, ale w przypadku 900–1000 wierszy nie powinno zająć to więcej niż 5 minut, a zwykle jest znacznie szybciej.

8. Uruchamianie wyszukiwania podobieństwa

Teraz możemy wykonać wyszukiwanie z użyciem wyszukiwania podobieństwa na podstawie wartości wektorowych obliczonych dla opisów i wartości wektorowej uzyskanej dla naszej prośby.

Zapytanie SQL można wykonać w tym samym interfejsie wiersza poleceń za pomocą polecenia gcloud sql connect lub alternatywnie w Cloud SQL Studio. Wszelkimi złożonymi zapytaniami z wieloma wierszami lepiej zarządzać w Cloud SQL Studio.

Uruchamianie Cloud SQL Studio

W konsoli kliknij wcześniej utworzoną instancję Cloud SQL.

b8d4844da1114a0b.png

Gdy otworzysz panel po prawej stronie, zobaczysz Cloud SQL Studio. Kliknij go.

ce3f27dc21367f2e.png

Otworzy się okno, w którym możesz podać nazwę bazy danych i swoje dane logowania:

  • Baza danych: quickstart_db
  • Użytkownik: postgres
  • Hasło: zanotowane hasło głównego użytkownika bazy danych.

Kliknij przycisk „AUTORYZUJ”.

2591c8bbc93e4e97.png

Otworzy się kolejne okno, w którym po prawej stronie kliknij kartę „Edytor”, aby otworzyć edytor kodu SQL.

74307cb101a3ba9d.png

Możemy już uruchomić zapytania.

Uruchom zapytanie

Wykonaj zapytanie, aby uzyskać listę dostępnych produktów najbardziej pasujących do żądań klienta. Żądanie, które przekażemy Vertex AI, aby uzyskać wartość wektora, brzmi mniej więcej tak: „Jakie drzewa owocowe dobrze rosną w tej okolicy?”

Oto zapytanie, które możesz wykonać, aby wybrać 10 elementów najbardziej pasujących do Twojego zapytania:

SELECT
        cp.product_name,
        left(cp.product_description,80) as description,
        cp.sale_price,
        cs.zip_code,
        (cp.embedding <=> embedding('text-embedding-004','What kind of fruit trees grow well here?')::vector) as distance
FROM
        cymbal_products cp
JOIN cymbal_inventory ci on
        ci.uniq_id=cp.uniq_id
JOIN cymbal_stores cs on
        cs.store_id=ci.store_id
        AND ci.inventory>0
        AND cs.store_id = 1583
ORDER BY
        distance ASC
LIMIT 10;

Skopiuj i wklej zapytanie do edytora Cloud SQL Studio i kliknij przycisk „Uruchom” lub wklej zapytanie w sesji wiersza poleceń, łącząc się z bazą danych quickstart_db.

cd07549522fd04c9.png

Oto lista wybranych produktów pasujących do zapytania.

product_name       |                                   description                                    | sale_price | zip_code |      distance       
-------------------------+----------------------------------------------------------------------------------+------------+----------+---------------------
 Cherry Tree             | This is a beautiful cherry tree that will produce delicious cherries. It is an d |      75.00 |    93230 | 0.43922018972266397
 Meyer Lemon Tree        | Meyer Lemon trees are California's favorite lemon tree! Grow your own lemons by  |         34 |    93230 |  0.4685112926118228
 Toyon                   | This is a beautiful toyon tree that can grow to be over 20 feet tall. It is an e |      10.00 |    93230 |  0.4835677149651668
 California Lilac        | This is a beautiful lilac tree that can grow to be over 10 feet tall. It is an d |       5.00 |    93230 |  0.4947204525907498
 California Peppertree   | This is a beautiful peppertree that can grow to be over 30 feet tall. It is an e |      25.00 |    93230 |  0.5054166905547247
 California Black Walnut | This is a beautiful walnut tree that can grow to be over 80 feet tall. It is a d |     100.00 |    93230 |  0.5084219510932597
 California Sycamore     | This is a beautiful sycamore tree that can grow to be over 100 feet tall. It is  |     300.00 |    93230 |  0.5140519790508755
 Coast Live Oak          | This is a beautiful oak tree that can grow to be over 100 feet tall. It is an ev |     500.00 |    93230 |  0.5143126438081371
 Fremont Cottonwood      | This is a beautiful cottonwood tree that can grow to be over 100 feet tall. It i |     200.00 |    93230 |  0.5174774727252058
 Madrone                 | This is a beautiful madrona tree that can grow to be over 80 feet tall. It is an |      50.00 |    93230 |  0.5227400803389093
(10 rows)

9. Poprawianie odpowiedzi LLM za pomocą odzyskanych danych

Możemy ulepszyć odpowiedź generatywnej AI LLM dla aplikacji klienta, korzystając z wyników wykonanego zapytania, i przygotować znaczące dane wyjściowe, używając podanych wyników zapytania jako części promptu dla generatywnego modelu językowego Vertex AI.

Aby to zrobić, musimy wygenerować dane w formacie JSON na podstawie wyników wyszukiwania wektorów, a potem użyć wygenerowanego ciągu JSON jako uzupełnienia promptu dla modelu LLM w Vertex AI, aby uzyskać sensowne dane wyjściowe. Najpierw generujemy plik JSON, potem testujemy go w Vertex AI Studio, a na końcu włączamy go w wyrażeniu SQL, które można wykorzystać w aplikacji.

Generowanie danych wyjściowych w formacie JSON

Zmodyfikuj zapytanie, aby wygenerować dane wyjściowe w formacie JSON i zwrócić tylko 1 wiersz, który ma zostać przekazany do Vertex AI.

Cloud SQL dla PostgreSQL

Oto przykład zapytania:

WITH trees as (
SELECT
        cp.product_name,
        left(cp.product_description,80) as description,
        cp.sale_price,
        cs.zip_code,
        cp.uniq_id as product_id
FROM
        cymbal_products cp
JOIN cymbal_inventory ci on
        ci.uniq_id=cp.uniq_id
JOIN cymbal_stores cs on
        cs.store_id=ci.store_id
        AND ci.inventory>0
        AND cs.store_id = 1583
ORDER BY
        (cp.embedding <=> embedding('text-embedding-004','What kind of fruit trees grow well here?')::vector) ASC
LIMIT 1)
SELECT json_agg(trees) FROM trees;

A tutaj oczekiwany kod JSON na wyjściu:

[{"product_name":"Cherry Tree","description":"This is a beautiful cherry tree that will produce delicious cherries. It is an d","sale_price":75.00,"zip_code":93230,"product_id":"d536e9e823296a2eba198e52dd23e712"}]

Uruchamianie promptu w Vertex AI Studio

Wygenerowany plik JSON możemy użyć jako część promptu do modelu tekstowego generatywnej AI w Vertex AI Studio.

Otwórz Vertex AI Studio Chat w konsoli Google Cloud.

449b5959fa0e93bd.png

Możesz otrzymać prośbę o włączenie dodatkowych interfejsów API, ale możesz ją zignorować. Do ukończenia laboratorium nie potrzebujemy żadnych dodatkowych interfejsów API.

Oto prompt, którego użyjemy:

You are a friendly advisor helping to find a product based on the customer's needs.
Based on the client request we have loaded a list of products closely related to search.
The list in JSON format with list of values like {"product_name":"name","description":"some description","sale_price":10,"zip_code": 10234, "produt_id": "02056727942aeb714dc9a2313654e1b0"}
Here is the list of products:
[place your JSON here]
The customer asked "What tree is growing the best here?"
You should give information about the product, price and some supplemental information.
Do not ask any additional questions and assume location based on the zip code provided in the list of products.

A tak wygląda kod, gdy zastąpimy placeholder JSON odpowiedzią na zapytanie:

You are a friendly advisor helping to find a product based on the customer's needs.
Based on the client request we have loaded a list of products closely related to search.
The list in JSON format with list of values like {"product_name":"name","description":"some description","sale_price":10,"zip_code": 10234, "produt_id": "02056727942aeb714dc9a2313654e1b0"}
Here is the list of products:
[{"product_name":"Cherry Tree","description":"This is a beautiful cherry tree that will produce delicious cherries. It is an d","sale_price":75.00,"zip_code":93230,"product_id":"d536e9e823296a2eba198e52dd23e712"}]
The customer asked "What tree is growing the best here?"
You should give information about the product, price and some supplemental information.
Do not ask any additional questions and assume location based on the zip code provided in the list of products.

A tak wygląda wynik, gdy uruchomimy prompt z wartościami JSON i modelem gemini-2.0-flash:

2c5145ebc04daae1.png

Oto odpowiedź, którą otrzymaliśmy od modelu w tym przykładzie. Pamiętaj, że Twoja odpowiedź może być inna ze względu na zmiany modelu i parametrów w czasie:

„Zgodnie z listą dostępnych produktów najlepszym drzewem, które może dobrze rosnąć w Twojej okolicy, jest drzewo wiśniowe”.

Kosztuje 75 USD.

Nie mam szczegółowych informacji o warunkach uprawy w Twoim kodzie pocztowym (93230), ale ogólnie wiadomo, że drzewa wiśniowe dobrze rosną w obszarach o umiarkowanym klimacie i dobrze odwodnionej glebie. Zwykle wymagają one zimnego okresu, aby wytworzyć owoce, więc należy o tym pamiętać. Mogą jednak stanowić wspaniały dodatek do ogrodu, zapewniając piękno i pyszne wiśnie, gdy tylko warunki są odpowiednie”.

Uruchamianie promptu w PSQL

Możemy też użyć integracji Cloud SQL AI z Vertex AI, aby uzyskać podobną odpowiedź od modelu generatywnego, używając SQL bezpośrednio w bazie danych. Aby jednak móc korzystać z modelu gemini-2.0-flash-exp, musimy go najpierw zarejestrować.

Uruchamianie w Cloud SQL for PostgreSQL

Zaktualizuj rozszerzenie do wersji 1.4.2 lub nowszej (jeśli bieżąca wersja jest starsza). Połącz się z bazą danych quickstart_db za pomocą polecenia gcloud sql connect (lub użyj Cloud SQL Studio) i wykonaj:

SELECT extversion from pg_extension where extname='google_ml_integration';

Jeśli zwrócona wartość jest mniejsza niż 1.4.2, wykonaj:

ALTER EXTENSION google_ml_integration UPDATE TO '1.4.2';

Następnie musimy ustawić flagę bazy danych google_ml_integration.enable_model_support na „on” (wł.). Możesz to zrobić w interfejsie konsoli internetowej lub uruchomić to polecenie gcloud.

gcloud sql instances patch my-cloudsql-instance \
--database-flags google_ml_integration.enable_model_support=on,cloudsql.enable_google_ml_integration=on

Wykonanie tego polecenia w tle zajmuje około 1–3 minut. Następnie możesz sprawdzić nową flagę w sesji psql lub za pomocą Cloud SQL Studio połączyć się z bazą danych quickstart_db.

show google_ml_integration.enable_model_support;

Oczekiwane dane wyjściowe z sesji psql to „on”:

quickstart_db => show google_ml_integration.enable_model_support;
 google_ml_integration.enable_model_support 
--------------------------------------------
 on
(1 row)

Następnie musimy zarejestrować 2 modele. Pierwszy z nich to już używany model text-embedding-004. Musisz go zarejestrować, ponieważ włączyliśmy możliwość rejestracji modeli.

Aby zarejestrować uruchomienie modelu w psql lub Cloud SQL Studio, użyj tego kodu:

CALL
  google_ml.create_model(
    model_id => 'text-embedding-004',
    model_provider => 'google',
    model_qualified_name => 'text-embedding-004',
    model_type => 'text_embedding',
    model_auth_type => 'cloudsql_service_agent_iam',
    model_in_transform_fn => 'google_ml.vertexai_text_embedding_input_transform',
    model_out_transform_fn => 'google_ml.vertexai_text_embedding_output_transform');

Kolejny model, który musimy zarejestrować, to gemini-2.0-flash-001. Będzie on używany do generowania przyjaznych dla użytkownika danych wyjściowych.

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

Listę zarejestrowanych modeli możesz sprawdzić w dowolnym momencie, wybierając informacje z google_ml.model_info_view.

select model_id,model_type from google_ml.model_info_view;

Oto przykładowe dane wyjściowe

quickstart_db=> select model_id,model_type from google_ml.model_info_view;
               model_id               |   model_type   
--------------------------------------+----------------
 textembedding-gecko                  | text_embedding
 textembedding-gecko@001              | text_embedding
 gemini-1.5-pro:streamGenerateContent | generic
 gemini-1.5-pro:generateContent       | generic
 gemini-1.0-pro:generateContent       | generic
 text-embedding-004                   | text_embedding
 gemini-2.0-flash-001                 | generic

Teraz możemy użyć danych wygenerowanych w subzapytaniu JSON, aby przekazać je jako część prompta do generatywnego modelu tekstowego AI za pomocą kodu SQL.

W sesji psql lub Cloud SQL Studio dotyczącej bazy danych wykonaj zapytanie.

WITH trees AS (
SELECT
        cp.product_name,
        cp.product_description AS description,
        cp.sale_price,
        cs.zip_code,
        cp.uniq_id AS product_id
FROM
        cymbal_products cp
JOIN cymbal_inventory ci ON
        ci.uniq_id = cp.uniq_id
JOIN cymbal_stores cs ON
        cs.store_id = ci.store_id
        AND ci.inventory>0
        AND cs.store_id = 1583
ORDER BY
        (cp.embedding <=> google_ml.embedding('text-embedding-004',
        'What kind of fruit trees grow well here?')::vector) ASC
LIMIT 1),
prompt AS (
SELECT
        'You are a friendly advisor helping to find a product based on the customer''s needs.
Based on the client request we have loaded a list of products closely related to search.
The list in JSON format with list of values like {"product_name":"name","product_description":"some description","sale_price":10}
Here is the list of products:' || json_agg(trees) || 'The customer asked "What kind of fruit trees grow well here?"
You should give information about the product, price and some supplemental information' AS prompt_text
FROM
        trees),
response AS (
SELECT
        json_array_elements(google_ml.predict_row( model_id =>'gemini-2.0-flash-001',
        request_body => json_build_object('contents',
        json_build_object('role',
        'user',
        'parts',
        json_build_object('text',
        prompt_text)))))->'candidates'->0->'content'->'parts'->0->'text' AS resp
FROM
        prompt)
SELECT
        string_agg(resp::text,
        ' ')
FROM
        response;

Oto oczekiwany wynik. Dane wyjściowe mogą się różnić w zależności od wersji modelu i parametrów:

"That" "'s a great question! Based on your location (assuming you're" " in zip code 93230), I have a suggestion for a" " fruit tree that should thrive.\n\nWe have the **Cherry Tree** available.\n\n**Product Name:** Cherry Tree\n\n**Description:** This is a beautiful cherry" " tree that will produce delicious cherries. It's a deciduous tree (meaning it loses its leaves in the fall) growing to about 15 feet tall." " The leaves are dark green in summer, turning a beautiful red in the fall. Cherry trees are known for their beauty, shade, and privacy.\n\n**Sale Price:** $75.00\n\n**Important Considerations for Growing" " Cherry Trees:**\n\n* **Climate:** Cherry trees prefer a cool, moist climate, and 93230 falls within a suitable range (USDA zones 4-9). However, it's always a good idea to" " check the specific microclimate of your property (sun exposure, drainage etc.).\n* **Soil:** They do best in sandy soil. If your soil is different, you may need to amend it to improve drainage.\n* **Pollination:** Many cherry varieties require a second, compatible cherry tree for proper pollination" ". Check the specific pollination needs of this variety before purchase if you want a significant cherry yield.\n\nThis cherry tree is a beautiful addition to any yard and will provide you with delicious cherries if you can meet its needs. Would you like to know more about its pollination requirements, or perhaps see if we have any other" " fruit trees suitable for your area?\n" ""

10. Tworzenie indeksu najbliższych sąsiadów

Nasz zbiór danych jest dość mały, a czas odpowiedzi zależy głównie od interakcji z modelami AI. Jednak gdy masz miliony wektorów, wyszukiwanie wektorów może zająć znaczną część czasu odpowiedzi i wygenerować duże obciążenie systemu. Aby to poprawić, możemy utworzyć indeks na podstawie naszych wektorów.

Tworzenie indeksu HNSW

W naszym teście użyjemy indeksu HNSW. HNSW to skrót od hierarchicznej, nawigowalnej małej sieci świata i reprezentuje wielowarstwowy indeks grafu.

Aby utworzyć indeks kolumny z wbudowanymi elementami, musimy zdefiniować tę kolumnę, funkcję odległości i opcjonalnie parametry takie jak m lub ef_constructions. Szczegółowe informacje o parametrach znajdziesz w dokumentacji.

CREATE INDEX cymbal_products_embeddings_hnsw ON cymbal_products
  USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

Oczekiwany wynik:

quickstart_db=> CREATE INDEX cymbal_products_embeddings_hnsw ON cymbal_products
  USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);
CREATE INDEX
quickstart_db=>

Porównaj odpowiedź

Teraz możemy uruchomić zapytanie wektorowe w trybie EXPLAIN i sprawdzić, czy został użyty indeks.

EXPLAIN (analyze) 
WITH trees as (
SELECT
        cp.product_name,
        left(cp.product_description,80) as description,
        cp.sale_price,
        cs.zip_code,
        cp.uniq_id as product_id
FROM
        cymbal_products cp
JOIN cymbal_inventory ci on
        ci.uniq_id=cp.uniq_id
JOIN cymbal_stores cs on
        cs.store_id=ci.store_id
        AND ci.inventory>0
        AND cs.store_id = 1583
ORDER BY
        (cp.embedding <=> embedding('text-embedding-004','What kind of fruit trees grow well here?')::vector) ASC
LIMIT 1)
SELECT json_agg(trees) FROM trees;

Oczekiwany wynik:

 Aggregate  (cost=779.12..779.13 rows=1 width=32) (actual time=1.066..1.069 rows=1 loops=1)
   ->  Subquery Scan on trees  (cost=769.05..779.12 rows=1 width=142) (actual time=1.038..1.041 rows=1 loops=1)
         ->  Limit  (cost=769.05..779.11 rows=1 width=158) (actual time=1.022..1.024 rows=1 loops=1)
               ->  Nested Loop  (cost=769.05..9339.69 rows=852 width=158) (actual time=1.020..1.021 rows=1 loops=1)
                     ->  Nested Loop  (cost=768.77..9316.48 rows=852 width=945) (actual time=0.858..0.859 rows=1 loops=1)
                           ->  Index Scan using cymbal_products_embeddings_hnsw on cymbal_products cp  (cost=768.34..2572.47 rows=941 width=941) (actual time=0.532..0.539 rows=3 loops=1)
                                 Order By: (embedding <=> '[0.008864171,0.03693164,-0.024245683,...
<redacted>
...,0.017593635,-0.040275685,-0.03914233,-0.018452475,0.00826032,-0.07372604
]'::vector)
                           ->  Index Scan using product_inventory_pkey on cymbal_inventory ci  (cost=0.42..7.17 rows=1 width=37) (actual time=0.104..0.104 rows=0 loops=3)
                                 Index Cond: ((store_id = 1583) AND (uniq_id = (cp.uniq_id)::text))
                                 Filter: (inventory > 0)
                                 Rows Removed by Filter: 1
                     ->  Materialize  (cost=0.28..8.31 rows=1 width=8) (actual time=0.133..0.134 rows=1 loops=1)
                           ->  Index Scan using product_stores_pkey on cymbal_stores cs  (cost=0.28..8.30 rows=1 width=8) (actual time=0.129..0.129 rows=1 loops=1)
                                 Index Cond: (store_id = 1583)
 Planning Time: 112.398 ms
 Execution Time: 1.221 ms

Z wyników widać wyraźnie, że zapytanie korzystało z „Index Scan using cymbal_products_embeddings_hnsw”.

A jeśli uruchomimy zapytanie bez wyjaśnienia:

WITH trees as (
SELECT
        cp.product_name,
        left(cp.product_description,80) as description,
        cp.sale_price,
        cs.zip_code,
        cp.uniq_id as product_id
FROM
        cymbal_products cp
JOIN cymbal_inventory ci on
        ci.uniq_id=cp.uniq_id
JOIN cymbal_stores cs on
        cs.store_id=ci.store_id
        AND ci.inventory>0
        AND cs.store_id = 1583
ORDER BY
        (cp.embedding <=> embedding('text-embedding-004','What kind of fruit trees grow well here?')::vector) ASC
LIMIT 1)
SELECT json_agg(trees) FROM trees;

Oczekiwany wynik:

[{"product_name":"Cherry Tree","description":"This is a beautiful cherry tree that will produce delicious cherries. It is an d","sale_price":75.00,"zip_code":93230,"product_id":"d536e9e823296a2eba198e52dd23e712"}]

Widzimy, że wynik jest taki sam i zwraca tę samą czereśnię, która była na szczycie w naszym wyszukiwaniu bez indeksu. W zależności od parametrów i typu indeksu wynik może się nieznacznie różnić. Podczas testów zapytanie z indeksem zwracało wyniki w 131,301 ms w porównaniu z 167,631 ms bez indeksu, ale mieliśmy do czynienia z bardzo małym zbiorem danych, a różnica byłaby większa w przypadku większych danych.

Możesz wypróbować różne indeksy dostępne dla wektorów oraz skorzystać z większej liczby laboratoriów i przykładów z integracją langchain, które znajdziesz w dokumentacji.

11. Czyszczenie środowiska

Usuwanie instancji Cloud SQL

Po zakończeniu ćwiczenia usuń instancję Cloud SQL

Jeśli połączenie zostało utracone i wszystkie poprzednie ustawienia zostały utracone, w Cloud Shell określ zmienne projektu i środowiska:

export INSTANCE_NAME=my-cloudsql-instance
export PROJECT_ID=$(gcloud config get-value project)

Usuń instancję:

gcloud sql instances delete $INSTANCE_NAME --project=$PROJECT_ID

Oczekiwane dane wyjściowe konsoli:

student@cloudshell:~$ gcloud sql instances delete $INSTANCE_NAME --project=$PROJECT_ID
All of the instance data will be lost when the instance is deleted.

Do you want to continue (Y/n)?  y

Deleting Cloud SQL instance...done.                                                                                                                
Deleted [https://sandbox.googleapis.com/v1beta4/projects/test-project-001-402417/instances/my-cloudsql-instance].

12. Gratulacje

Gratulujemy ukończenia ćwiczenia.

Omówione zagadnienia

  • Wdrażanie instancji Cloud SQL for PostgreSQL
  • Jak utworzyć bazę danych i włączyć integrację z Cloud SQL AI
  • Wczytywanie danych do bazy danych
  • Jak używać modelu wektorów dystrybucyjnych Vertex AI w Cloud SQL
  • Jak wzbogacić wynik za pomocą modelu generatywnego Vertex AI
  • Jak zwiększyć wydajność dzięki indeksowi wektorowemu

Wypróbuj podobny case study dotyczący AlloyDB z indeksem ScaNN zamiast HNSW.

13. Ankieta

Dane wyjściowe:

Jak będziesz korzystać z tego samouczka?

Tylko przeczytaj Przeczytaj i wykonaj ćwiczenia