Aplikacja do wyszukiwania zabawek z bazami danych w chmurze, środowiskiem uruchomieniowym bez serwera i integracjami z oprogramowaniem open source

1. Omówienie

Wyobraź sobie, że wchodzisz do sklepu z zabawkami, gdzie bez trudu znajdziesz idealny prezent. Możesz opisać, czego szukasz, przesłać zdjęcie zabawki lub nawet zaprojektować własną kreację. Sklep natychmiast zrozumie Twoje potrzeby i zaoferuje Ci spersonalizowane usługi. To nie jest futurystyczna wizja, tylko rzeczywistość oparta na sztucznej inteligencji, technologii chmurowej i wizji spersonalizowanego e-commerce.

Wyzwanie: znalezienie idealnego produktu, który spełnia Twoje oczekiwania, może być trudne. Ogólne wyszukiwane hasła, słowa kluczowe i wyszukiwanie z nieprecyzyjnym dopasowaniem często nie wystarczają, przeglądanie nieskończonej liczby stron może być żmudne, a rozbieżność między tym, co sobie wyobrażamy, a czym jest dostępne, może prowadzić do frustracji.

Rozwiązanie: aplikacja demonstracyjna rozwiązuje to wyzwanie, wykorzystując możliwości AI, aby zapewnić naprawdę spersonalizowane i płynne działanie wyszukiwarki kontekstowej oraz niestandardowe generowanie produktu dopasowanego do kontekstu wyszukiwania.

Co utworzysz

W ramach tego laboratorium wykonasz te czynności:

  1. Tworzenie instancji AlloyDB i wczytywanie zbioru danych Toys
  2. Włączanie w AlloyDB rozszerzeń pgvector i modeli generatywnej AI
  3. Generowanie wektorów na podstawie opisu produktu i wykonywanie w czasie rzeczywistym wyszukiwania zbliżonego do skojarzeń z użyciem podobieństwa cosinusowego w przypadku tekstu wyszukiwanego przez użytkownika.
  4. Wywołaj Gemini 2.0 Flash, aby opisać obraz przesłany przez użytkownika w ramach wyszukiwania zabawek w kontekście
  5. Użyj Imagen 3, aby stworzyć zabawkę na podstawie zainteresowań użytkownika
  6. Uruchom narzędzie do prognozowania cen utworzone za pomocą Gen AI Toolbox for Databases, aby uzyskać informacje o cenach zabawki stworzonej przez użytkownika.
  7. Wdrażanie rozwiązania w bezserwerowych funkcjach Cloud Run

Wymagania

  • przeglądarka, np. Chrome lub Firefox;
  • projekt Google Cloud z włączonymi płatnościami;

2. Architektura

Przepływ danych: przyjrzyjmy się bliżej temu, jak dane przemieszczają się w naszym systemie:

  1. Wyszukiwanie kontekstowe z wykorzystaniem AI i techniki RAG (Retrieval Augmented Generation)

Wyobraź sobie, że zamiast szukania po prostu „czerwony samochód” system rozumie to:

„Mały pojazd odpowiedni dla 3-letniego chłopca”.

AlloyDB jako podstawa: używamy AlloyDB, w pełni zarządzanej bazy danych Google Cloud zgodnej z PostgreSQL, do przechowywania danych o zabawkach, w tym opisów, adresów URL obrazów i innych odpowiednich atrybutów.

pgvector do wyszukiwania semantycznego: rozszerzenie PostgreSQL pgvector umożliwia przechowywanie wektorów dystrybucyjnych zarówno opisów zabawek, jak i zapytań użytkowników. Umożliwia to wyszukiwanie semantyczne, co oznacza, że system rozumie znaczenie słów, a nie tylko ścisłe słowa kluczowe.

Podobne cosinusowe do trafności: używamy podobieństwa cosinusowego do pomiaru podobieństwa semantycznego między wektorem wyszukiwania użytkownika a wektorami opisów zabawek, aby wyświetlać najbardziej trafne wyniki.

Indeks ScaNN dla szybkości i dokładności: aby zapewnić szybkie i dokładne wyniki, zwłaszcza w przypadku rosnącego asortymentu zabawek, integrujemy indeks ScaNN (Scalable Nearest Neighbors). Dzięki temu znacznie zwiększyliśmy wydajność i odtwarzalność wyszukiwania wektorów.

  1. Wyszukiwanie i analizowanie obrazów za pomocą Gemini 2.0 Flash

Załóżmy, że zamiast wpisywać kontekst w postaci tekstu użytkownik chce przesłać zdjęcie znanej zabawki, której chce użyć do wyszukiwania. Użytkownicy mogą przesłać zdjęcie ulubionej zabawki i w ten sposób uzyskać odpowiednie funkcje. Korzystamy z modelu Gemini 2.0 Flash firmy Google, wywoływanego za pomocą LangChain4j, aby analizować obraz i wyodrębniać odpowiedni kontekst, taki jak kolor, materiał, typ zabawki i przeznaczona grupa wiekowa.

  1. Tworzenie zabawki marzeń z użyciem generatywnej AI: Imagen 3

Prawdziwa magia dzieje się wtedy, gdy użytkownicy decydują się na stworzenie własnej zabawki. Dzięki Imagen 3 dzieci mogą opisać swoją wymarzoną zabawkę, korzystając z prostych promptów tekstowych. Wyobraź sobie, że możesz powiedzieć: „Chcę pluszowego smoka z fioletowymi skrzydłami i przyjaznym obliczem” i zobaczyć, jak smok ożywa na ekranie. Następnie Imagen 3 generuje obraz niestandardowej zabawki, dzięki czemu użytkownik może wyraźnie zobaczyć swoje dzieło.

  1. Prognozowanie cen za pomocą agentów i narzędzia generatywnej AI do baz danych

Wprowadziliśmy funkcję prognozowania cen, która szacuje koszt produkcji niestandardowej zabawki. Jest ona obsługiwana przez agenta, który zawiera zaawansowane narzędzie do obliczania cen.

Generatywna AI Toolbox dla baz danych: ten agent jest płynnie zintegrowany z naszą bazą danych za pomocą nowego narzędzia open source od Google, czyli GenAI Toolbox dla baz danych. Dzięki temu agent ma dostęp do danych w czasie rzeczywistym dotyczących kosztów materiałów, procesów produkcyjnych i innych istotnych czynników, aby podać dokładną szacunkową cenę. Więcej informacji znajdziesz tutaj.

  1. Java Spring Boot, Gemini Code Assist i Cloud Run do usprawnionego tworzenia i wdrażania bezserwerowego

Cała aplikacja została utworzona za pomocą Java Spring Boot, stabilnej i skalowalnej platformy. Korzystaliśmy z Gemini Code Assist w całym procesie tworzenia, zwłaszcza w przypadku front-endu, co znacznie przyspieszyło cykl rozwoju i poprawiło jakość kodu. Do wdrożenia całej aplikacji użyliśmy Cloud Run, a do wdrożenia bazy danych i funkcji agenta jako niezależnych punktów końcowych – funkcji Cloud Run.

3. Zanim zaczniesz

Utwórz projekt

  1. W konsoli Google Cloud na stronie selektora projektu wybierz lub utwórz projekt Google Cloud.
  2. Sprawdź, czy w projekcie Cloud włączone są płatności. Dowiedz się, jak sprawdzić, czy w projekcie są włączone płatności .
  3. Użyjesz Cloud Shell, czyli środowiska wiersza poleceń działającego w Google Cloud, które jest wstępnie załadowane w bq. Kliknij Aktywuj Cloud Shell u góry konsoli Google Cloud.

Obraz przycisku aktywowania Cloud Shell

  1. Po połączeniu z Cloud Shell sprawdź, czy jesteś już uwierzytelniony i czy projekt jest ustawiony na identyfikator Twojego projektu, używając tego polecenia:
gcloud auth list
  1. Aby sprawdzić, czy polecenie gcloud zna Twój projekt, uruchom w Cloud Shell to polecenie:
gcloud config list project
  1. Jeśli projekt nie jest ustawiony, użyj tego polecenia:
gcloud config set project <YOUR_PROJECT_ID>
  1. Włącz wymagane interfejsy API, uruchamiając po kolei te polecenia w terminalu Cloud Shell:

Możesz też użyć pojedynczego polecenia, aby wykonać te czynności, ale jeśli korzystasz z konta próbnego, podczas zbiorczego włączania tych funkcji możesz napotkać problemy z kwotą. Dlatego polecenia są oddzielone po jednym na wiersz.

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

Alternatywą dla polecenia gcloud jest konsola, w której możesz wyszukać poszczególne usługi lub skorzystać z tego linku.

Jeśli pominiesz któryś interfejs API, możesz go włączyć w trakcie implementacji.

Więcej informacji o poleceniach i użytkowaniu gcloud znajdziesz w dokumentacji.

4. Konfiguracja bazy danych

W tym module użyjemy bazy danych AlloyDB do przechowywania danych sklepu z zabawkami. Do przechowywania wszystkich zasobów, takich jak bazy danych i logi, używa klastrów. Każdy klaster ma instancję główną, która stanowi punkt dostępu do danych. W tabelach będą się znajdować rzeczywiste dane.

Utwórz klaster, instancję i tabelę AlloyDB, do której zostanie załadowany zbiór danych e-commerce.

Tworzenie klastra i instancji

  1. Otwórz stronę AlloyDB w konsoli Cloud. Najłatwiej znaleźć większość stron w Cloud Console jest za pomocą paska wyszukiwania w konsoli.
  2. Na tej stronie wybierz UTWÓRZ KLASTER:

f76ff480c8c889aa.png

  1. Zobaczysz ekran podobny do tego poniżej. Utwórz klaster i instancję z tymi wartościami (upewnij się, że wartości są takie same, jeśli klonujesz kod aplikacji z repozytorium):
  • Identyfikator klastra:vector-cluster
  • hasło: „alloydb”.
  • Zgodność z PostgreSQL 15
  • Region:us-central1”.
  • Networking: „default”.

538dba58908162fb.png

  1. Po wybraniu sieci domyślnej zobaczysz ekran podobny do tego poniżej.

Wybierz SKONFIGUROWANIE POŁĄCZENIA.
7939bbb6802a91bf.png

  1. Następnie wybierz „Użyj automatycznie przydzielonego zakresu adresów IP” i kliknij Dalej. Po sprawdzeniu informacji wybierz UTWÓRZ POŁĄCZENIE. 768ff5210e79676f.png
  2. Po skonfigurowaniu sieci możesz kontynuować tworzenie klastra. Kliknij UTWÓRZ KLASTER, aby dokończyć konfigurowanie klastra, jak pokazano poniżej:

e06623e55195e16e.png

Pamiętaj, aby zmienić identyfikator instancji na „

vector-instance"

Pamiętaj, że utworzenie klastra zajmie około 10 minut. Po zakończeniu procesu powinien wyświetlić się ekran z ogólnymi informacjami o kreatorzym utworzonym klastrze.

5. Pozyskiwanie danych

Teraz dodaj tabelę z danymi o sklepie. Przejdź do AlloyDB, wybierz klaster główny, a następnie AlloyDB Studio:

847e35f1bf8a8bd8.png

Możesz musieć poczekać, aż instancja zostanie utworzona. Gdy to zrobisz, zaloguj się w AlloyDB, używając danych logowania utworzonych podczas tworzenia klastra. Do uwierzytelniania w PostgreSQL użyj tych danych:

  • Nazwa użytkownika: „postgres
  • Baza danych: „postgres
  • Hasło: „alloydb

Gdy uwierzytelnisz się w AlloyDB Studio, polecenia SQL są wpisywane w Edytorze. Możesz dodać większą liczbę okien Edytora, klikając plus po prawej stronie ostatniego okna.

91a86d9469d499c4.png

Polecenia AlloyDB wpisujesz w oknach edytora, korzystając w razie potrzeby z opcji Uruchom, Formatuj i Wyczyść.

Włączanie rozszerzeń

Do tworzenia tej aplikacji użyjemy rozszerzeń pgvectorgoogle_ml_integration. Rozszerzenie pgvector umożliwia przechowywanie wektorów dystrybucyjnych i wyszukiwanie ich. Rozszerzenie google_ml_integration udostępnia funkcje, których używasz do uzyskiwania dostępu do punktów końcowych prognozowania Vertex AI w celu uzyskiwania prognoz w SQL. Włącz te rozszerzenia, uruchamiając te DDL:

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

Jeśli chcesz sprawdzić rozszerzenia włączone w bazie danych, uruchom to polecenie SQL:

select extname, extversion from pg_extension;

Tworzenie tabeli

Utwórz tabelę za pomocą poniższej instrukcji 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)) ;

Po wykonaniu tego polecenia powinna się ona wyświetlić w bazie danych.

Pozyskiwanie danych

W tym module mamy dane testowe w postaci około 72 rekordów w pliku SQL. Zawiera ona pola id, name, description, quantity, price, image_url. Pozostałe pola zostaną wypełnione później w ramach tego modułu.

Skopiuj z tego miejsca wiersze lub instrukcje insert, a potem wklej je na pustą kartę edytora i kliknij URUCHOM.

Aby zobaczyć zawartość tabeli, rozwiń sekcję Eksplorator, aż zobaczysz tabelę o nazwie Odzież. Kliknij 3 kropki (⋮), aby wyświetlić opcję Zapytanie o tabelę. W nowej karcie Edytor otworzy się instrukcja SELECT.

cfaa52b717f9aaed.png

Przyznaj uprawnienia

Aby przyznać użytkownikowi postgres uprawnienia do wykonywania funkcji embedding, uruchom to polecenie:

GRANT EXECUTE ON FUNCTION embedding TO postgres;

Przypisz rolę Vertex AI USER do konta usługi AlloyDB

Otwórz terminal Cloud Shell i wpisz to polecenie:

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. Tworzenie wektorów kontekstu

Komputery znacznie łatwiej przetwarzają liczby niż tekst. System umieszczania konwertuje tekst w serię liczb zmiennoprzecinkowych, które powinny reprezentować tekst, niezależnie od tego, jak jest sformułowany, w jakim języku jest itd.

Możesz opisać miejsce nad morzem. Może to być „nad wodą”, „przy plaży”, „tylko krok od oceanu”, „sur la mer”, „на берегу океана” itd. Wszystkie te terminy wyglądają inaczej, ale ich znaczenie semantyczne lub, mówiąc językiem uczenia maszynowego, ich wektory wejściowe powinny być bardzo podobne.

Gdy dane i kontekst będą gotowe, uruchomimy zapytanie SQL, aby dodać do tabeli w polu embedding kody osadzania opisu produktu. Możesz używać różnych modeli wstawiania. Używamy text-embedding-005 z Vertex AI. Pamiętaj, aby używać tego samego modelu w całym projekcie.

Uwaga: jeśli używasz istniejącego projektu Google Cloud utworzonego jakiś czas temu, być może nadal musisz używać starszych wersji modelu osadzania tekstu, np. textembedding-gecko.

Wróć do karty AlloyDB Studio i wpisz ten kod DML:

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

Aby zobaczyć niektóre wstępnie przetworzone dane, ponownie spójrz na tabelę toys. Aby zobaczyć zmiany, ponownie uruchom instrukcję SELECT.

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

Powinien on zwrócić wektor zagęszczenia, który wygląda jak tablica liczb zmiennoprzecinkowych, dla opisu zabawki, jak pokazano poniżej:

7d32f7cd7204e1f3.png

Uwaga: nowo utworzone projekty Google Cloud w wersji bezpłatnej mogą mieć problemy z limitem liczby żądań na potrzeby umieszczania w treści w modelu umieszczania w treści na sekundę. Zalecamy użycie zapytania filtra dla identyfikatora, a potem wybranie 1–5 rekordów itd. podczas generowania kodu embed.

7. Wykonywanie wyszukiwania wektorowego

Teraz, gdy tabela, dane i wektory są gotowe, przeprowadźmy wyszukiwanie wektorów w czasie rzeczywistym dla tekstu wyszukiwanego przez użytkownika.

Załóżmy, że użytkownik zadaje pytanie:

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

Możesz znaleźć dopasowania, uruchamiając to zapytanie:

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;

Przyjrzyjmy się temu zapytaniu bliżej:

W tym zapytaniu

  1. Tekst wyszukiwania użytkownika: „I want a white plush teddy bear toy with a floral pattern.”.
  2. Przekształcamy je w embeddingi w ramach metody embedding(), używając modelu text-embedding-005. Ten krok powinien być Ci już znany z ostatniego kroku, w którym zastosowaliśmy funkcję umieszczania we wszystkich elementach w tabeli.
  3. <=>” oznacza użycie metody odległości COSINY SIMILARITY. Wszystkie dostępne miary podobieństwa znajdziesz w dokumentacji pgvector.
  4. Przekształcamy wynik metody osadzania w typ wektora, aby był zgodny z wektorami zapisanymi w bazie danych.
  5. LIMIT 5 oznacza, że chcemy wyodrębnić 5 najbliższych sąsiadów dla tekstu wyszukiwania.

Wynik wygląda tak:

fa7f0fc3a4c68804.png

Jak widać w wynikach, dopasowania są bardzo zbliżone do tekstu wyszukiwania. Zmień tekst, aby zobaczyć, jak zmieniają się wyniki.

Ważna uwaga:

Załóżmy, że chcemy zwiększyć wydajność (czas zapytania), wydajność i odtwarzalność wyników wyszukiwania wektorowego za pomocą indeksu ScaNN. Aby porównać wyniki z wykorzystaniem indeksu i bez niego, wykonaj czynności opisane w tym poście na blogu.

Krok opcjonalny: zwiększanie efektywności i odtwarzalności dzięki indeksowi ScaNN

Dla wygody podajemy tutaj kroki tworzenia indeksu:

  1. Ponieważ mamy już utworzone klastry, instancje, konteksty i wkłady, wystarczy zainstalować rozszerzenie ScaNN za pomocą tego polecenia:
CREATE EXTENSION IF NOT EXISTS alloydb_scann;
  1. Następnie utworzymy indeks (ScaNN):
CREATE INDEX toysearch_index ON toys
USING scann (text_embeddings cosine)
WITH (num_leaves=9);

W powyższym pliku DDL parametr apparel_index to nazwa indeksu.

„toys” to moja tabela

„scann” to metoda indeksowania

„Embedding” to kolumna w tabeli, którą chcę zindeksować

„cosinus” to metoda odległości, której chcę użyć w indeksie.

„8” to liczba partycji, które mają być stosowane do tego indeksu. Ustaw dowolną wartość od 1 do 1048576. Więcej informacji o tym, jak określić tę wartość, znajdziesz w artykule Dostosowanie indeksu ScaNN.

Użyłem kwadratowego pierwiastka z liczby punktów danych, zgodnie z zaleceniami w repozytorium ScaNN (podczas partycjonowania parametr num_leaves powinien być zbliżony do kwadratowego pierwiastka z liczby punktów danych).

  1. Sprawdź, czy indeks został utworzony, używając zapytania:
SELECT * FROM pg_stat_ann_indexes;
  1. Wykonaj wyszukiwanie wektorowe, używając tego samego zapytania, które zostało użyte bez indeksu:
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;

Powyższe zapytanie jest takie samo jak zapytanie użyte w laboratorium w kroku 8. Teraz jednak to pole jest zindeksowane.

  1. Przetestuj proste zapytanie z indeksem oraz bez niego (przez pominięcie indeksu):

W tym przypadku mamy tylko 72 rekordy, więc indeks nie ma większego znaczenia. W przypadku testu przeprowadzonego w innym przypadku użycia wyniki wyglądają tak:

To samo zapytanie wyszukiwania wektorowego dotyczące zaindeksowanych danych z wektorami dystrybucyjnymi zapewnia większą wydajność i jakość wyników wyszukiwania. Wydajność jest znacznie wyższa (w ujęciu czasu wykonania: 10,37 ms bez ScaNN i 0,87 ms z ScaNN) dzięki indeksowi. Więcej informacji na ten temat znajdziesz w tym poście na blogu.

8. Weryfikacja dopasowania za pomocą LLM

Zanim przejdziemy do tworzenia usługi, która zwraca najlepsze dopasowania do aplikacji, użyjemy generatywnego modelu AI, aby sprawdzić, czy te potencjalne odpowiedzi są naprawdę trafne i czy można je udostępnić użytkownikowi.

Sprawdzanie, czy instancja jest skonfigurowana pod kątem Gemini

Najpierw sprawdź, czy integracja z Google ML jest już włączona w przypadku Twojego klastra i instancji. W AlloyDB Studio wpisz to polecenie:

show google_ml_integration.enable_model_support;

Jeśli wartość jest wyświetlana jako "on" (włączone), możesz pominąć 2 kolejne kroki i przejść bezpośrednio do konfigurowania integracji AlloyDB i modelu Vertex AI.

  1. Otwórz instancję główną klastra AlloyDB i kliknij EDYTUJ INSTANSĘ GŁÓWNĄ.

cb76b934ba3735bd.png

  1. W sekcji Opcje konfiguracji zaawansowanej przejdź do sekcji Flagi. i upewnij się, że google_ml_integration.enable_model_support flag jest ustawiony na „on”, jak pokazano poniżej:

6a59351fcd2a9d35.png

Jeśli nie jest ustawione na „wł.”, ustaw je na „wł.”, a potem kliknij przycisk ZAKTUALIZUJ INSTANCJĘ. Ten krok zajmie kilka minut.

Integracja AlloyDB z modelem Vertex AI

Teraz możesz połączyć się z AlloyDB Studio i uruchomić to zdanie DML, aby skonfigurować dostęp do modelu Gemini z AlloyDB. W odpowiednim miejscu użyj identyfikatora projektu. Zanim uruchomisz polecenie, możesz zobaczyć ostrzeżenie o błędzie składni, ale powinno ono działać prawidłowo.

Najpierw tworzymy połączenie z modelem Gemini 1.5, jak pokazano poniżej. W poniższym poleceniu zastąp fragment $PROJECT_ID identyfikatorem projektu Google Cloud.

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');

Aby sprawdzić modele skonfigurowane pod kątem dostępu, uruchom w AlloyDB Studio to polecenie:

select model_id,model_type from google_ml.model_info_view;        

Na koniec musimy przyznać użytkownikom bazy danych uprawnienia do wykonywania funkcji ml_predict_row, aby mogli wykonywać prognozy za pomocą modeli Google Vertex AI. Uruchom to polecenie:

GRANT EXECUTE ON FUNCTION ml_predict_row to postgres;

Uwaga: jeśli używasz istniejącego projektu Google Cloud i istniejącego klastra lub wystąpienia AlloyDB utworzonego jakiś czas temu, może być konieczne usunięcie starych odwołań do modelu gemini-1.5 i utworzenie go ponownie za pomocą powyższego polecenia CALL oraz ponowne uruchomienie grant execute na funkcji ml_predict_row, jeśli wystąpią problemy z nadchodzącymi wywołaniami gemini-1.5.

Ocenianie odpowiedzi

W następnej sekcji użyjemy dużego zapytania, aby uzyskać odpowiednie odpowiedzi, ale może być ono trudne do zrozumienia. Za kilka minut przyjrzymy się elementom i sprawdzimy, jak się łączą.

  1. Najpierw wysyłamy do bazy danych żądanie, aby uzyskać 10 najbardziej pasujących do zapytania użytkownika wyników.
  2. Aby określić, na ile odpowiedzi są prawidłowe, użyjemy zapytania zewnętrznego, w którym wyjaśnimy, jak je oceniać. W zapytaniu jest używane pole recommended_text, które jest tekstem wyszukiwania, oraz pole content (które jest polem opisu zabawki) z tabeli wewnętrznej.
  3. Następnie sprawdzimy, jak dobre są zwrócone odpowiedzi.
  4. Funkcja predict_row zwraca wynik w formacie JSON. Kod „-> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'"” służy do wyodrębniania rzeczywistego tekstu z tego pliku JSON. Aby zobaczyć zwrócony kod JSON, możesz usunąć ten kod.
  5. Aby otrzymać odpowiedź LLM, wyodrębniamy ją za pomocą funkcji 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%';

Chociaż może się to wydawać przytłaczające, mam nadzieję, że uda Ci się to lepiej zrozumieć. Wyniki pokazują, czy istnieje dopasowanie, jaki jest jego odsetek i jaki jest to rodzaj oceny.

Zwróć uwagę, że model Gemini domyślnie ma włączone przesyłanie strumieniowe, więc rzeczywista odpowiedź jest rozłożona na kilka wierszy:

c2b006aeb3f3a2fc.png

9. Przeniesienie wyszukiwarki zabawek do Cloud Serverless

Czy chcesz opublikować tę aplikację w internecie? Aby utworzyć bezserwerowy mechanizm wyszukiwania z funkcjami Cloud Run, wykonaj te czynności:

  1. Aby UTWORZYĆ nową funkcję Cloud Run, otwórz Cloud Run Functions w konsoli Google Cloud lub użyj linku https://console.cloud.google.com/functions/add.
  2. Jako środowisko wybierz „funkcja Cloud Run”. Podaj nazwę funkcji „get-toys-alloydb” i wybierz region „us-central1”. Ustaw uwierzytelnianie na „Zezwalaj na nieuwierzytelnione wywołania” i kliknij DALEJ. Jako środowisko uruchomieniowe wybierz Java 17, a jako edytor kodu źródłowego – Edytor wbudowany.
  3. Domyślnie punkt wejścia ma wartość „gcfv2.HelloHttpFunction”. Zastąp kod zastępczy w elementach HelloHttpFunction.javapom.xml funkcji Cloud Run kodem z pliku HelloHttpFunction.java i odpowiednio z pliku pom.xml.
  4. Pamiętaj, aby w pliku Java zastąpić obiekt zastępczy <<YOUR_PROJECT>> i dane logowania do AlloyDB swoimi wartościami. Dane logowania do AlloyDB to te, których użyliśmy na początku tego Codelab. Jeśli użyjesz innych wartości, zmień je w pliku Java.
  5. Kliknij Wdróż.

Po wdrożeniu, aby umożliwić funkcji Cloud Functions dostęp do instancji bazy danych AlloyDB, utworzymy oprogramowanie sprzęgające VPC.

WAŻNY KROK:

Po wdrożenie funkcji powinny być widoczne w konsoli Cloud Run Functions. Odszukaj nowo utworzoną funkcję (get-toys-alloydb), kliknij ją, a następnie kliknij EDYTUJ i zmień te ustawienia:

  1. Otwórz Ustawienia środowiska wykonawczego, kompilacji, połączeń i zabezpieczeń
  2. Zwiększ czas oczekiwania do 180 sekund
  3. Otwórz kartę POŁĄCZENIA:

4e83ec8a339cda08.png

  1. W ustawieniach Ingress sprawdź, czy wybrana jest opcja „Zezwalaj na cały ruch”.
  2. W ustawieniach wyjścia kliknij menu Sieć i wybierz opcję „Dodaj nowy łącznik VPC” i postępuj zgodnie z instrukcjami wyświetlanymi w wyskakującym okienku:

8126ec78c343f199.png

  1. Podaj nazwę oprogramowania sprzęgającego VPC i upewnij się, że region jest taki sam jak w przypadku instancji. Pozostaw wartość sieci jako domyślną, a podsieć ustaw jako niestandardowy zakres adresów IP z zakresem adresów 10.8.0.0 lub podobnym, który jest dostępny.
  2. Rozwiń opcję POKAZ USTAWIENIA SKALOWANIA i upewnij się, że konfiguracja jest skonfigurowana dokładnie w ten sposób:

7baf980463a8a5c.png

  1. Kliknij UTWÓRZ. Ten łącznik powinien teraz być widoczny w ustawieniach wychodzących.
  2. Wybierz nowo utworzone oprogramowanie sprzęgające
  3. Wybierz opcję kierowania całego ruchu przez to oprogramowanie sprzęgające VPC.
  4. Kliknij kolejno DALEJ i WDRÓŻ.

10. Testowanie funkcji Cloud Run

Po wdrożeniu zaktualizowanej funkcji w Cloud Functions punkt końcowy powinien mieć następujący format:

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

Funkcję Cloud Run możesz też przetestować w ten sposób:

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 .

Wynik:

23861e9091565a64.png

Znakomicie. Tak łatwo można przeprowadzić wyszukiwanie wektorów podobieństwa za pomocą modelu wektorów dystrybucyjnych na danych w AlloyDB.

11. Tworzenie klienta aplikacji internetowej

W tej części zbudujemy aplikację internetową, z którą użytkownik będzie mógł wchodzić w interakcje i znajdować pasujące zabawki na podstawie tekstu lub obrazu, a nawet tworzyć nowe zabawki zgodnie ze swoimi potrzebami. Aplikacja została już skompilowana, więc możesz wykonać podane niżej czynności, aby skopiować ją do środowiska IDE i uruchomić.

  1. Korzystamy z Gemini 2.0 Flash, aby opisać obraz, który użytkownik może przesłać, aby znaleźć pasujące zabawki. Dlatego potrzebujemy klucza API dla tej aplikacji. Aby to zrobić, otwórz stronę https://aistudio.google.com/apikey i pobierz klucz API dla aktywnego projektu Google Cloud, w którym wdrażasz tę aplikację. Zapisz klucz w bezpiecznym miejscu:

ae2db169e6a94e4a.png

  1. Przechodzenie do terminala Cloud Shell
  2. Sklonuj repozytorium za pomocą tego polecenia:
git clone https://github.com/AbiramiSukumaran/toysearch

cd toysearch
  1. Po sklonowaniu repozytorium powinieneś mieć dostęp do projektu w edytorze Cloud Shell.
  2. Z projektu klonowanego musisz usunąć foldery „get-toys-alloydb” i „toolbox-toys”, ponieważ zawierają one kod funkcji Cloud Run, do którego można się odwoływać z repozytorium, gdy zajdzie taka potrzeba.
  3. Zanim skompilujesz i wdrożysz aplikację, upewnij się, że ustawiono wszystkie niezbędne zmienne środowiskowe. Otwórz terminal Cloud Shell i wykonaj te czynności:
PROJECT_ID=$(gcloud config get-value project)

export PROJECT_ID $PROJECT_ID

export GOOGLE_API_KEY <YOUR API KEY that you saved>
  1. Kompilowanie i uruchamianie aplikacji lokalnie:

Upewnij się, że jesteś w katalogu projektu, i uruchom te polecenia:

mvn package

mvn spring-boot:run 
  1. Wdrażanie w Cloud Run
gcloud run deploy --source .

12. Informacje o generatywnej AI

Nie musisz niczego robić. Informacyjnie:

Teraz, gdy masz już aplikację do wdrożenia, zatrzymaj się na chwilę, aby dowiedzieć się, jak udało nam się przeprowadzić wyszukiwanie (tekstu i obrazu) oraz wygenerować wyniki.

  1. Wyszukiwanie wektorowe na podstawie tekstu użytkownika:

Ten problem został już rozwiązany w funkcjach Cloud Run, które wdrożyliśmy w sekcji „Zastosowanie wyszukiwania wektorowego w internecie”.

  1. Wyszukiwanie wektorów na podstawie przesłanego obrazu:

Załóżmy, że zamiast wpisywać kontekst w postaci tekstu użytkownik chce przesłać zdjęcie znanej zabawki, której chce użyć do wyszukiwania. Użytkownicy mogą przesłać zdjęcie ulubionej zabawki i w ten sposób uzyskać odpowiednie funkcje.

Korzystamy z modelu Gemini 2.0 Flash firmy Google, wywoływanego za pomocą LangChain4j, aby analizować obraz i wyodrębniać odpowiedni kontekst, np. kolor, materiał, typ zabawki i przeznaczoną grupę wiekową.

W zaledwie 5 krokach przekształciliśmy dane multimodalne użytkownika w wyniki dopasowania za pomocą wywołania dużego modelu językowego przy użyciu frameworku open source. Dowiedz się, jak to zrobić:

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. Dowiedz się, jak wykorzystaliśmy Imagen 3 do stworzenia zabawki na zamówienie na podstawie prośby użytkownika za pomocą generatywnej AI.

Następnie Imagen 3 generuje obraz niestandardowej zabawki, dzięki czemu użytkownik może wyraźnie zobaczyć swoje dzieło. Oto, jak to zrobiliśmy w zaledwie 5 krokach:

// 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();
    }
  }

Prognoza ceny

W poprzedniej sekcji omawialiśmy, jak Imagen generuje obraz zabawki, którą użytkownik chce zaprojektować samodzielnie. Aby można było je kupić, aplikacja musi określić ich cenę. Użyliśmy intuicyjnej logiki, aby określić cenę zabawek wykonywanych na zamówienie. Zasada polega na użyciu średniej ceny 5 najbardziej pasujących zabawek (pod względem opisu) do zabawki, którą użytkownik zaprojektował.

Prognoza ceny wygenerowanej zabawki jest ważną częścią tej aplikacji. Do jej wygenerowania użyliśmy podejścia agentycznego. Przedstawiamy zestaw narzędzi generatywnej AI do obsługi baz danych.

13. Gen AI Toolbox for Databases

Gen AI Toolbox for Databases to serwer open source od Google, który ułatwia tworzenie narzędzi generatywnej AI do interakcji z bazami danych. Dzięki temu możesz tworzyć narzędzia łatwiej, szybciej i bezpieczniej, ponieważ biblioteka ta obsługuje złożone zadania, takie jak łączenie połączeń i uwierzytelnianie. Pomaga tworzyć narzędzia generatywnej AI, które umożliwiają agentom dostęp do danych w bazie danych.

Aby przygotować narzędzie i utworzyć naszego agenta aplikacji, wykonaj te czynności: Link do Codelab w Toolbox

Aplikacja może teraz używać wdrożonego punktu końcowego funkcji Cloud Run do wypełniania ceny wraz z wygenerowanym wynikiem Imagen w przypadku obrazu zabawki wykonanej na zamówienie.

14. Testowanie aplikacji internetowej

Wszystkie komponenty aplikacji zostały już utworzone i wdrożone, więc aplikacja jest gotowa do obsługi w chmurze. przetestować aplikację we wszystkich scenariuszach. Oto link do filmu, który pokazuje, czego możesz się spodziewać:

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

Oto jak wygląda strona docelowa:

241db19e7176e93e.png

15. Czyszczenie danych

Aby uniknąć obciążenia konta Google Cloud opłatami za zasoby wykorzystane w tym poście, wykonaj te czynności:

  1. W konsoli Google Cloud otwórz stronę Zarządzanie zasobami.
  2. Na liście projektów wybierz projekt do usunięcia, a potem kliknij Usuń.
  3. W oknie wpisz identyfikator projektu i kliknij Wyłącz, aby usunąć projekt.

16. Gratulacje

Gratulacje! Udało Ci się przeprowadzić wyszukiwanie i generowanie kontekstowe w Toystore przy użyciu AlloyDB, pgvector, Imagen i Gemini 2.0, korzystając z bibliotek open source do tworzenia niezawodnych integracji. Połączenie możliwości AlloyDB, Vertex AI i wyszukiwania wektorowego pozwoliło nam zrobić ogromny krok naprzód w czynieniu wyszukiwania kontekstowego i wektorowego bardziej dostępnym, wydajnym i naprawdę ukierunkowanym na znaczenie.