אפליקציית חיפוש בחנות צעצועים עם מסדי נתונים ב-Cloud, סביבות זמן ריצה ללא שרת (serverless) ושילובים של קוד פתוח

1. סקירה כללית

נסו לדמיין שאתם נכנסים לחנות צעצועים באופן וירטואלי או פיזי, ומוצאים את המתנה המושלמת בקלות. אתם יכולים לתאר את מה שאתם מחפשים, להעלות תמונה של צעצוע או אפילו לעצב יצירה משלכם, והחנות תבין את הצרכים שלכם באופן מיידי ותספק חוויה מותאמת אישית. זה לא פנטזיה עתידנית, אלא מציאות שמבוססת על AI, טכנולוגיית ענן ותפיסה של מסחר אלקטרוני מותאם אישית.

האתגר: לפעמים קשה למצוא את המוצר המושלם שמתאים לחזון שלכם. מונחי חיפוש כלליים, מילות מפתח וחיפושים לא מדויקים לא תמיד מספקים את מה שרוצים למצוא, גלישה בדפים אינסופיים יכולה להיות משעממת והפער בין מה שרוצים למצוא לבין מה שזמין יכול לגרום לתסכול.

הפתרון: באפליקציית הדגמה אנחנו מתמודדים עם האתגר הזה באופן ישיר, ומנצלים את יכולות ה-AI כדי לספק חוויה מותאמת אישית ואינטראקטיבית באמצעות חיפוש לפי הקשר ויצירה בהתאמה אישית של המוצר שמתאים להקשר החיפוש.

מה תפַתחו

במסגרת שיעור ה-Lab הזה תלמדו:

  1. יצירת מכונה של AlloyDB וטעינה של מערך הנתונים Toys
  2. הפעלת התוספים pgvector ו-AI גנרטיבי ב-AlloyDB
  3. יצירת הטמעות (embeddings) מתיאורי המוצר וביצוע חיפוש של דמיון קוסינוס בזמן אמת לטקסט החיפוש של המשתמשים
  4. הפעלת Gemini 2.0 Flash כדי לתאר את התמונה שהמשתמש העלאה לחיפוש צעצועים לפי הקשר
  5. שימוש ב-Imagen 3 כדי ליצור צעצוע בהתאמה אישית על סמך תחומי העניין של המשתמש
  6. קריאה לכלי לחיזוי מחירים שנוצר באמצעות Gen AI Toolbox for Databases כדי לקבל פרטי מחיר של הצעצוע שנוצר בהתאמה אישית
  7. פריסת הפתרון ב-Cloud Run Functions ללא שרת

דרישות

  • דפדפן, כמו Chrome או Firefox
  • פרויקט ב-Google Cloud שבו החיוב מופעל.

2. ארכיטקטורה

תעבורת הנתונים: נבחן לעומק איך הנתונים עוברים במערכת שלנו:

  1. חיפוש לפי הקשר עם RAG מבוסס-AI (יצירה משופרת של אחזור)

אפשר להסתכל על זה כך: במקום לחפש רק 'רכב אדום', המערכת מבינה את הדברים הבאים:

"כלי רכב קטן שמתאים לילד בן 3".

AlloyDB כבסיס: אנחנו משתמשים ב-AlloyDB, מסד הנתונים המנוהל של Google Cloud שתואם ל-PostgreSQL, כדי לאחסן את נתוני הצעצועים שלנו, כולל תיאורים, כתובות URL של תמונות ומאפיינים רלוונטיים אחרים.

pgvector לחיפוש סמנטי: pgvector הוא תוסף ל-PostgreSQL שמאפשר לנו לאחסן הטמעות של וקטורים של תיאורי צעצועים ושל שאילתות חיפוש של משתמשים. כך מתאפשר חיפוש סמנטי, כלומר המערכת מבינה את המשמעות של המילים, ולא רק את מילות המפתח המדויקות.

דמיון קוסינוס לרלוונטיות: אנחנו משתמשים בדמיון קוסינוס כדי למדוד את הדמיון הסמנטי בין וקטור החיפוש של המשתמש לבין וקטורי התיאורים של הצעצועים, וכך להציג את התוצאות הרלוונטיות ביותר.

אינדקס ScaNN למהירות ולדיוק: כדי להבטיח תוצאות מהירות ומדויקות, במיוחד כשמלאי הצעצועים שלנו גדל, אנחנו משלבים את האינדקס ScaNN (שכנים קרובים שניתן להתאמה). כך אפשר לשפר משמעותית את היעילות ואת היכולת לזכור את תוצאות החיפוש בעזרת וקטורים.

  1. חיפוש מבוסס-תמונה והבנה בעזרת Gemini 2.0 Flash

במקום להקליד את ההקשר כטקסט, נניח שהמשתמש רוצה להעלות תמונה של צעצוע מוכר שהוא רוצה לחפש באמצעותו. המשתמשים יכולים להעלות תמונה של צעצוע שהם אוהבים ולקבל תכונות רלוונטיות. אנחנו משתמשים במודל Gemini 2.0 Flash של Google, שמופעל באמצעות LangChain4j, כדי לנתח את התמונה ולחלץ את ההקשר הרלוונטי, כמו הצבע, החומר, הסוג וקבוצת הגיל של הצעצוע.

  1. יצירת צעצוע החלומות בהתאמה אישית באמצעות AI גנרטיבי: Imagen 3

הקסם האמיתי קורה כשהמשתמשים מחליטים ליצור צעצוע משלהם. בעזרת Imagen 3, אנחנו מאפשרים להם לתאר את צעצוע החלומות שלהם באמצעות הנחיות טקסט פשוטות. איך זה נשמע לכם: אתם יכולים להגיד "אני רוצה דרקון קטן מפלצת עם כנפיים סגולות ופנים ידידותיות" ולראות את הדרקון הזה מופיע במסך! לאחר מכן, Imagen 3 יוצר תמונה של הצעצוע שעוצב בהתאמה אישית, ומאפשר למשתמש לראות בבירור את היצירה שלו.

  1. חיזוי מחירים באמצעות סוכנים ו-Gen AI Toolbox למסדי נתונים

הטמענו תכונה של חיזוי מחירים שמאפשרת להעריך את העלות של ייצור הצעצוע בהתאמה אישית. הכלי הזה פועל באמצעות סוכן שכולל כלי מתוחכם לחישוב מחירים.

Gen AI Toolbox for Databases: הסוכן הזה משולב בצורה חלקה במסד הנתונים שלנו באמצעות הכלי החדש של Google בקוד פתוח, Gen AI Toolbox for Databases. כך הנציג יכול לגשת לנתונים בזמן אמת לגבי עלויות החומרים, תהליכי הייצור וגורמים רלוונטיים אחרים כדי לספק אומדן מחיר מדויק. מידע נוסף זמין כאן.

  1. Java Spring Boot, ‏ Gemini Code Assist ו-Cloud Run לפיתוח יעיל ופריסה ללא שרת

האפליקציה כולה בנויה באמצעות Java Spring Boot, מסגרת חזקה וניתנת להתאמה. השתמשנו ב-Gemini Code Assist לאורך כל תהליך הפיתוח, במיוחד בפיתוח הקצה הקדמי, כדי לזרז משמעותית את מחזור הפיתוח ולשפר את איכות הקוד. השתמשנו ב-Cloud Run כדי לפרוס את האפליקציה כולה, וב-Cloud Run Functions כדי לפרוס את מסדי הנתונים והפונקציות של הסוכן בתור נקודות קצה עצמאיות.

3. לפני שמתחילים

יצירת פרויקט

  1. בדף לבחירת הפרויקט במסוף Google Cloud, בוחרים או יוצרים פרויקט ב-Google Cloud.
  2. הקפידו לוודא שהחיוב מופעל בפרויקט שלכם ב-Cloud. כך בודקים אם החיוב מופעל בפרויקט
  3. נשתמש ב-Cloud Shell, סביבת שורת פקודה שפועלת ב-Google Cloud ומגיעה עם bq טעון מראש. לוחצים על Activate Cloud Shell בחלק העליון של מסוף Google Cloud.

תמונה של לחצן הפעלת Cloud Shell

  1. אחרי שמתחברים ל-Cloud Shell, בודקים שכבר בוצע אימות ושהמזהה של הפרויקט מוגדר כפרויקט באמצעות הפקודה הבאה:
gcloud auth list
  1. מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שהפקודה gcloud מכירה את הפרויקט.
gcloud config list project
  1. אם הפרויקט לא מוגדר, משתמשים בפקודה הבאה כדי להגדיר אותו:
gcloud config set project <YOUR_PROJECT_ID>
  1. מפעילים את ממשקי ה-API הנדרשים על ידי הרצת הפקודות הבאות בטרמינל של Cloud Shell, אחת אחרי השנייה:

יש גם פקודה אחת להפעלת הפקודות הבאות, אבל אם אתם משתמשים בחשבון לניסיון, יכול להיות שתתקלו בבעיות שקשורות למכסות אם תנסו להפעיל אותן בכמות גדולה. לכן, כל פקודה מופיעה בשורה נפרדת.

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. הגדרת מסד נתונים

בשיעור ה-Lab הזה נשתמש ב-AlloyDB כמסד הנתונים שיאחסן את הנתונים של חנות הצעצועים. הוא משתמש באשכולות כדי לאחסן את כל המשאבים, כמו מסדי נתונים יומנים. לכל אשכול יש מכונה ראשית שמספקת נקודת גישה לנתונים. הטבלאות יכללו את הנתונים בפועל.

נלמד ליצור אשכול, מכונה וטבלה ב-AlloyDB שבהם ייטען מערך הנתונים של המסחר האלקטרוני.

יצירת אשכול ומכונה

  1. נכנסים לדף AlloyDB במסוף Cloud. דרך קלה למצוא את רוב הדפים במסוף Cloud היא לחפש אותם באמצעות סרגל החיפוש במסוף.
  2. בוחרים באפשרות CREATE CLUSTER (יצירת אשכול) בדף הזה:

f76ff480c8c889aa.png

  1. יוצג מסך כמו זה שבהמשך. יוצרים אשכול ומכונה עם הערכים הבאים (חשוב לוודא שהערכים תואמים במקרה שמשכפלים את קוד האפליקציה מהמאגר):
  • cluster id:‏ "vector-cluster"
  • password: "alloydb"
  • תואם ל-PostgreSQL 15
  • אזור: "us-central1"
  • Networking: "default"

538dba58908162fb.png

  1. כשבוחרים את רשת ברירת המחדל, מוצג מסך כמו זה שבהמשך.

בוחרים באפשרות הגדרת חיבור.
7939bbb6802a91bf.png

  1. לאחר מכן, בוחרים באפשרות Use an automatically allocated IP range (שימוש בטווח כתובות IP שהוקצה באופן אוטומטי) וממשיכים. אחרי שבודקים את המידע, בוחרים באפשרות 'יצירת חיבור'. 768ff5210e79676f.png
  2. אחרי שמגדירים את הרשת, אפשר להמשיך ליצור את האשכולות. לוחצים על CREATE CLUSTER (יצירת אשכול) כדי להשלים את הגדרת האשכול, כפי שמתואר בהמשך:

e06623e55195e16e.png

חשוב לשנות את מזהה המכונה ל-"

vector-instance"

.

לתשומת ליבכם: יצירת האשכולות תימשך כ-10 דקות. לאחר השלמת הפעולה, אמור להופיע מסך עם סקירה כללית של האשכולות שיצרתם.

5. הטמעת נתונים

עכשיו הגיע הזמן להוסיף טבלה עם הנתונים על החנות. עוברים אל AlloyDB, בוחרים את האשכול הראשי ואז את AlloyDB Studio:

847e35f1bf8a8bd8.png

יכול להיות שתצטרכו להמתין עד לסיום יצירת המכונה. לאחר מכן, נכנסים ל-AlloyDB באמצעות פרטי הכניסה שיצרתם כשיצרתם את האשכולות. משתמשים בנתונים הבאים כדי לבצע אימות ב-PostgreSQL:

  • שם משתמש : "postgres"
  • מסד נתונים : 'postgres'
  • סיסמה : "alloydb"

אחרי שתבצעו אימות ב-AlloyDB Studio, תוכלו להזין פקודות SQL בעורך. אפשר להוסיף כמה חלונות של הכלי באמצעות סמל הפלוס שמשמאל לחלון האחרון.

91a86d9469d499c4.png

מזינים פקודות ל-AlloyDB בחלונות העריכה, באמצעות האפשרויות Run (הפעלה), Format (פורמט) ו-Clear (ניקוי) לפי הצורך.

הפעלת תוספים

כדי ליצור את האפליקציה הזו, נשתמש בתוספים 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)) ;

אם הפקודה שלמעלה תתבצע בהצלחה, אמורה להופיע לכם הטבלה במסד הנתונים.

הטמעת נתונים

בשיעור ה-Lab הזה יש לנו נתוני בדיקה של כ-72 רשומות בקובץ ה-SQL הזה. הוא מכיל את השדות id, name, description, quantity, price, image_url. השדות האחרים יאוכלסו בשלב מאוחר יותר במהלך הסדנה.

מעתיקים משם את השורות או את הוראות ההוספה, מדביקים את השורות האלה בכרטיסייה ריקה של עורך ומקישים על 'הפעלה'.

כדי לראות את תוכן הטבלה, מרחיבים את הקטע 'כלי המחקר' עד שמופיעה הטבלה apparels. לוחצים על סמל הנקודות השלושה (⋮) כדי להציג את האפשרות לשלוח שאילתה לטבלה. תנאי SELECT ייפתח בכרטיסייה חדשה ב-Editor.

cfaa52b717f9aaed.png

מתן הרשאה

מריצים את ההצהרה הבאה כדי להעניק למשתמש postgres זכויות הפעלה על הפונקציה embedding:

GRANT EXECUTE ON FUNCTION embedding TO postgres;

הקצאת התפקיד Vertex AI User לחשבון השירות של AlloyDB

נכנסים לטרמינל של 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. יצירת הטמעות (embeddings) להקשר

קל יותר למחשבים לעבד מספרים מאשר לעבד טקסט. מערכת הטמעה ממירה טקסט לסדרה של מספרים בנקודה צפה, שאמורים לייצג את הטקסט, ללא קשר לאופן שבו הוא מנוסח, לשפה שבה הוא כתוב וכו'.

כדאי לתאר מיקום על חוף הים. הוא יכול להיקרא 'על המים', 'חוף הים', 'הליכה מהחדר לאוקיינוס', 'sur la mer', 'на берегу океана' וכו'. כל המונחים האלה נראים שונים, אבל המשמעות הסמנטית שלהם, או במונחים של למידת מכונה, ההטמעות שלהם אמורות להיות קרובות מאוד זו לזו.

עכשיו, כשהנתונים וההקשר מוכנים, נריץ את שאילתת ה-SQL כדי להוסיף את הטמעות התיאור של המוצר לטבלה בשדה embedding. יש מגוון מודלים להטמעה שאפשר להשתמש בהם. אנחנו משתמשים ב-text-embedding-005 מ-Vertex AI. חשוב להשתמש באותו מודל הטמעה לאורך כל הפרויקט.

הערה: אם אתם משתמשים בפרויקט קיים ב-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;

הפונקציה הזו אמורה להחזיר את וקטור הטמעת הנתונים (embedding), שנראה כמו מערך של מספרים שמוצגים בספרות עשרוניות, של תיאור הצעצוע, כפי שמוצג בהמשך:

7d32f7cd7204e1f3.png

הערה: פרויקטים חדשים ב-Google Cloud שנוצרו ברמת התמחור 'חינם' עשויים להיתקל בבעיות במכסות לגבי מספר הבקשות להטמעה שמותר לשלוח בשנייה למודל ההטמעה. מומלץ להשתמש בשאילתת סינון למזהה, ואז לבחור באופן סלקטיבי 1-5 רשומות וכן הלאה, בזמן יצירת ההטמעה.

7. ביצוע חיפוש Vector

עכשיו, כשהטבלה, הנתונים וההטמעות מוכנים, נבצע חיפוש וקטורים בזמן אמת של טקסט החיפוש של המשתמש.

נניח שהמשתמש שואל:

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

כדי למצוא התאמות לכך, מריצים את השאילתה הבאה:

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

נבחן את השאילתה הזו בפירוט:

בשאילתה הזו,

  1. טקסט החיפוש של המשתמש הוא: "I want a white plush teddy bear toy with a floral pattern."
  2. אנחנו ממירים אותו להטמעות (embeddings) בשיטה embedding() באמצעות המודל: text-embedding-005. השלב הזה אמור להיראות מוכר אחרי השלב האחרון, שבו החלנו את פונקציית ההטמעה על כל הפריטים בטבלה.
  3. הערך '<=>' מייצג את השימוש בשיטת המרחק COSINE SIMILARITY. כל מדדי הדמיון הזמינים מפורטים במסמכי התיעוד של pgvector.
  4. אנחנו ממירים את התוצאה של שיטת ההטמעה לסוג וקטור כדי שהיא תהיה תואמת לווקטורים שמאוחסנים במסד הנתונים.
  5. LIMIT 5 מייצג שאנחנו רוצים לחלץ 5 שכנים קרובים לטקסט החיפוש.

התוצאה נראית כך:

fa7f0fc3a4c68804.png

כפי שניתן לראות בתוצאות, ההתאמות דומות מאוד לטקסט החיפוש. נסו לשנות את הטקסט כדי לראות איך התוצאות משתנות.

הערה חשובה:

עכשיו נניח שאנחנו רוצים לשפר את הביצועים (זמן השאילתה), את היעילות ואת החזרה של תוצאת החיפוש בעזרת וקטורים באמצעות האינדקס ScaNN. כדי להשוות בין התוצאה עם המדד לבין התוצאה ללא המדד, יש לפעול לפי השלבים שמפורטים בפוסט הזה בבלוג.

שלב אופציונלי: שיפור היעילות וההחזקה בזיכרון באמצעות מדד ScaNN

לשם נוחות, ריכזנו כאן את השלבים ליצירת האינדקס:

  1. מאחר שכבר יצרנו את האשכולות, המכונות, ההקשר וההטמעות, אנחנו צריכים רק להתקין את התוסף ScaNN באמצעות ההצהרה הבאה:
CREATE EXTENSION IF NOT EXISTS alloydb_scann;
  1. בשלב הבא נוצר את האינדקס (ScaNN):
CREATE INDEX toysearch_index ON toys
USING scann (text_embeddings cosine)
WITH (num_leaves=9);

ב-DDL שלמעלה, apparel_index הוא שם האינדקס

"toys" היא הטבלה שלי

"scann" היא שיטת האינדקס

'embedding' היא העמודה בטבלה שאליה אני רוצה ליצור אינדקס

'cosine' היא שיטת המרחק שבה אני רוצה להשתמש עם האינדקס

הערך '8' הוא מספר המחיצות שיחולו על האינדקס הזה. מגדירים ערך בין 1 ל-1048576. מידע נוסף על בחירת הערך הזה זמין במאמר התאמת אינדקס ScaNN.

השתמשתי בשורש הריבועי של מספר נקודות הנתונים, כפי שמומלץ ב-ScaNN repo (כשמבצעים חלוקה למחיצות, הערך של num_leaves צריך להיות בערך שורש הריבועי של מספר נקודות הנתונים).

  1. בודקים אם האינדקס נוצר באמצעות השאילתה:
SELECT * FROM pg_stat_ann_indexes;
  1. מבצעים חיפוש וקטורים באמצעות אותה שאילתה שבה השתמשנו בלי האינדקס:
select * from toys
ORDER BY text_embeddings <=> CAST(embedding('text-embedding-005', 'I want a white plush teddy bear toy with a floral pattern') as vector(768))
LIMIT 5;

השאילתה שלמעלה היא אותה שאילתה שבה השתמשנו בסדנה בשלב 8. עם זאת, עכשיו השדה נוסף לאינדקס.

  1. בודקים עם שאילתת חיפוש פשוטה עם האינדקס ובלי האינדקס (על ידי השמטת האינדקס):

בתרחיש לדוגמה הזה יש רק 72 רשומות, ולכן האינדקס לא באמת נכנס לתוקף. בבדיקה שנערכה בתרחיש לדוגמה אחר, התוצאות הן:

אותה שאילתת חיפוש וקטורי על נתוני הטמעה (embedding) שנוספו לאינדקס מניבה תוצאות חיפוש איכותיות ויעילות. היעילות משתפרת בצורה משמעותית (מבחינת זמן הביצוע: 10.37 אלפיות השנייה ללא ScaNN ו-0.87 אלפיות השנייה עם ScaNN) עם האינדקס. מידע נוסף בנושא הזה זמין בפוסט הזה בבלוג.

8. אימות התאמה באמצעות LLM

לפני שנמשיך ונוצר שירות להצגת התאמות מושלמות לאפליקציה, נשתמש במודל AI גנרטיבי כדי לאמת אם התשובות האפשריות האלה רלוונטיות באמת ובטוחות לשיתוף עם המשתמש.

מוודאים שהמכונה מוגדרת ל-Gemini

קודם צריך לבדוק אם השילוב של Google ML כבר מופעל באשכול ובמכונה. ב-AlloyDB Studio, מריצים את הפקודה הבאה:

show google_ml_integration.enable_model_support;

אם הערך שמוצג הוא "on", אפשר לדלג על שני השלבים הבאים ולעבור ישירות להגדרת השילוב של AlloyDB עם Vertex AI Model.

  1. עוברים למכונה הראשית של אשכול AlloyDB ולוחצים על 'עריכת המכונה הראשית'.

cb76b934ba3735bd.png

  1. עוברים לקטע Flags (דגלים) באפשרויות ההגדרה המתקדמות. ומוודאים שהערך של google_ml_integration.enable_model_support flag מוגדר כ-on, כפי שמופיע בהמשך:

6a59351fcd2a9d35.png

אם האפשרות לא מוגדרת כ'מופעלת', מגדירים אותה כ'מופעלת' ולוחצים על הלחצן UPDATE INSTANCE. השלב הזה יימשך כמה דקות.

שילוב של AlloyDB עם מודלים של Vertex AI

עכשיו אפשר להתחבר ל-AlloyDB Studio ולהריץ את משפט ה-DML הבא כדי להגדיר גישה למודל Gemini מ-AlloyDB, באמצעות מזהה הפרויקט במקום הנדרש. יכול להיות שתופיע אזהרה על שגיאת תחביר לפני הרצת הפקודה, אבל היא אמורה לפעול בצורה תקינה.

קודם כול, יוצרים את החיבור של מודל Gemini 1.5 כפי שמתואר בהמשך. חשוב לזכור להחליף את $PROJECT_ID בפקודה הבאה במזהה הפרויקט ב-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');

אפשר לבדוק את המודלים שהוגדרו לצורך גישה באמצעות הפקודה הבאה ב-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 שלמעלה, ולהריץ שוב את grant execute על הפונקציה ml_predict_row, למקרה שתתקלו בבעיות בהפעלות הבאות של gemini-1.5.

בדיקת התשובות

בסוף נשתמש בשאילתה אחת גדולה בקטע הבא כדי לוודא שהתשובות מהשאילתה הן סבירות, אבל יכול להיות שיהיה קשה להבין את השאילתה. עכשיו נבחן את החלקים ונראה איך הם מתחברים בעוד כמה דקות.

  1. קודם נשלחת בקשה למסד הנתונים כדי לקבל את 10 ההתאמות הקרובות ביותר לשאילתה של המשתמש.
  2. כדי לקבוע את מידת התוקף של התשובות, נשתמש בשאילתה חיצונית שבה נסביר איך להעריך את התשובות. השאילתה כוללת את השדה recommended_text, שהוא טקסט החיפוש, ואת השדה content (שדה תיאור הצעצוע) של הטבלה הפנימית.
  3. לאחר מכן נבדוק את 'האיכות' של התשובות שהתקבלו.
  4. הפונקציה predict_row מחזירה את התוצאה שלה בפורמט JSON. הקוד -> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'" משמש לחילוץ הטקסט בפועל מה-JSON הזה. כדי לראות את ה-JSON בפועל שמוחזר, אפשר להסיר את הקוד הזה.
  5. לבסוף, כדי לקבל את התשובה של ה-LLM, אנחנו מחלצים אותה באמצעות REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g')
SELECT id,
       name,
       content,
       quantity,
       price,
       image_url,
       recommended_text,
       REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') AS gemini_validation
  FROM (SELECT id,
               name,
               content,
               quantity,
               price,
               image_url,
               recommended_text,
               CAST(ARRAY_AGG(LLM_RESPONSE) AS TEXT) AS gemini_validation
          FROM (SELECT id,
                       name,
                       content,
                       quantity,
                       price,
                       image_url,
                       recommended_text,
                       json_array_elements(google_ml.predict_row(model_id => 'gemini-1.5',
                                                                   request_body => CONCAT('{ "contents": [ { "role": "user", "parts": [ { "text": "User wants to buy a toy and this is the description of the toy they wish to buy: ',                                                                                              recommended_text,                                                                                              '. Check if the following product items from the inventory are close enough to really, contextually match the user description. Here are the items: ',                                                                                         content,                                                                                         '. Return a ONE-LINE response with 3 values: 1) MATCH: if the 2 contexts are reasonably matching in terms of any of the color or color family specified in the list, approximate style match with any of the styles mentioned in the user search text: This should be a simple YES or NO. Choose NO only if it is completely irrelevant to users search criteria. 2) PERCENTAGE: percentage of match, make sure that this percentage is accurate 3) DIFFERENCE: A clear one-line easy description of the difference between the 2 products. Remember if the user search text says that some attribute should not be there, and the record has it, it should be a NO match. " } ] } ] }')::JSON)) -> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text' :: TEXT AS LLM_RESPONSE
                  FROM (SELECT id,
                               name,
                               description AS content,
                               quantity,
                               price,
                               image_url,
                               'Pink panther standing' AS recommended_text
                          FROM toys
                         ORDER BY text_embeddings <=> embedding('text-embedding-005',
                                                                'Pink panther standing')::VECTOR
                         LIMIT 10) AS xyz) AS X
         GROUP BY id,
                  name,
                  content,
                  quantity,
                  price,
                  image_url,
                  recommended_text) AS final_matches
 WHERE REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') LIKE '%MATCH%:%YES%';

יכול להיות שהתהליך עדיין נראה מרתיע, אבל אני מקווה שזה יעזור לך להבין אותו טוב יותר. בתוצאות יצוין אם יש התאמה או לא, אחוז ההתאמה והסבר כללי על הדירוג.

שימו לב שהסטרימינג מופעל כברירת מחדל במודל Gemini, כך שהתשובה בפועל מפוזרת על פני כמה שורות:

c2b006aeb3f3a2fc.png

9. העברת חיפוש הצעצועים ל-Cloud ללא שרת

רוצים להעביר את האפליקציה הזו לאינטרנט? כדי להפוך את מנוע הידע הזה ללא שרת באמצעות Cloud Run Functions:

  1. עוברים אל Cloud Run Functions במסוף Google Cloud כדי ליצור פונקציית Cloud Run חדשה, או משתמשים בקישור: https://console.cloud.google.com/functions/add.
  2. בוחרים את הסביבה כ-פונקציית Cloud Run. נותנים לפונקציה את השם get-toys-alloydb ובוחרים את האזור us-central1. מגדירים את האימות לאפשרות 'אישור קריאות לא מאומתות' ולוחצים על הבא. בוחרים ב-Java 17 כסביבת זמן ריצה וב-Inline Editor לקוד המקור.
  3. כברירת מחדל, נקודת הכניסה תוגדר כ-gcfv2.HelloHttpFunction. מחליפים את קוד placeholder ב-HelloHttpFunction.java וב-pom.xml של פונקציית Cloud Run בקוד מ-HelloHttpFunction.java ומ-pom.xml, בהתאמה.
  4. חשוב לזכור לשנות את ה-placeholder‏ <<YOUR_PROJECT>> ואת פרטי הכניסה ל-AlloyDB בערכים שלכם בקובץ Java. פרטי הכניסה ל-AlloyDB הם אלה שבהם השתמשנו בתחילת סדנת הקוד הזו. אם השתמשתם בערכים שונים, עליכם לשנות אותם בקובץ Java.
  5. לוחצים על פריסת.

לאחר הפריסה, כדי לאפשר ל-Cloud Function לגשת למכונה של מסד הנתונים AlloyDB, נוצר את מחבר ה-VPC.

שלב חשוב:

אחרי שתסיימו את תהליך הפריסה, הפונקציות אמורות להופיע במסוף Cloud Run Functions של Google. מחפשים את הפונקציה שנוצרה (get-toys-alloydb), לוחצים עליה ואז על עריכה ומשנים את הפרטים הבאים:

  1. מעבר להגדרות של סביבת זמן ריצה, build, חיבורים ואבטחה
  2. הגדלת הזמן הקצוב לתפוגה ל-180 שניות
  3. עוברים לכרטיסייה CONNECTIONS (חיבורים):

4e83ec8a339cda08.png

  1. בהגדרות Ingress, מוודאים שהאפשרות Allow all traffic (אישור כל התנועה) מסומנת.
  2. בקטע Egress settings (הגדרות תעבורת נתונים יוצאת), לוחצים על התפריט הנפתח Network (רשת) ובוחרים באפשרות Add New VPC Connector (הוספת מחבר VPC חדש). פועלים לפי ההוראות שמופיעות בתיבת הדו-שיח הקופצת:

8126ec78c343f199.png

  1. נותנים שם למחבר VPC ומוודאים שהאזור זהה לאזור של המכונה. משאירים את הערך של Network כברירת מחדל ומגדירים את Subnet כטווח IP מותאם אישית עם טווח ה-IP 10.8.0.0 או משהו דומה שזמין.
  2. מרחיבים את האפשרות SHOW SCALING SETTINGS (הצגת הגדרות התאמה לעומס) ומוודאים שההגדרות מוגדרות בדיוק כך:

7baf980463a86a5c.png

  1. לוחצים על 'יצירה' והמחבר הזה אמור להופיע בהגדרות תעבורת הנתונים היוצאת.
  2. בוחרים את המחבר החדש שנוצר.
  3. בוחרים שכל התנועה תנותב דרך המחבר הזה של VPC.
  4. לוחצים על הבא ואז על פריסה.

10. בדיקה של פונקציית Cloud Run

אחרי הפריסה של פונקציית Cloud Functions המעודכנת, נקודת הקצה אמורה להופיע בפורמט הבא:

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

לחלופין, אפשר לבדוק את פונקציית Cloud Run באופן הבא:

PROJECT_ID=$(gcloud config get-value project)

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

והתוצאה:

23861e9091565a64.png

זהו! כך פשוט לבצע חיפוש דמיון וקטורי באמצעות מודל הטמעת הנתונים (Embeddings) בנתונים של AlloyDB.

11. פיתוח הלקוח של אפליקציית האינטרנט

בקטע הזה נבנה אפליקציית אינטרנט שמאפשרת למשתמש לקיים אינטראקציה ולמצוא צעצועים תואמים על סמך טקסט, תמונה ואפילו ליצור צעצוע חדש על סמך הצרכים שלו. מכיוון שהאפליקציה כבר נוצרה, תוכלו לפעול לפי השלבים הבאים כדי להעתיק אותה לסביבת הפיתוח המשולבת (IDE) ולהפעיל את האפליקציה.

  1. אנחנו משתמשים ב-Gemini 2.0 Flash כדי לתאר את התמונה שהמשתמשים עשויים להעלות כדי למצוא צעצועים תואמים, ולכן אנחנו צריכים לקבל את מפתח ה-API של האפליקציה הזו. כדי לעשות זאת, עוברים אל https://aistudio.google.com/apikey ומקבלים את מפתח ה-API של הפרויקט הפעיל ב-Google Cloud שבו מטמיעים את האפליקציה הזו, ושומרים את המפתח במקום כלשהו:

ae2db169e6a94e4a.png

  1. ניווט לטרמינל של Cloud Shell
  2. מעתיקים את המאגר באמצעות הפקודה הבאה:
git clone https://github.com/AbiramiSukumaran/toysearch

cd toysearch
  1. אחרי השלמת היצירה של העותק, אמורה להיות לכם גישה לפרויקט דרך Cloud Shell Editor.
  2. צריך למחוק את התיקיות "get-toys-alloydb" ו-"toolbox-toys" מהפרויקט שהועתק, כי שתי התיקיות האלה מכילות קוד של Cloud Run Functions, שאפשר להפנות אליו מהמאגר כשצריך.
  3. לפני שמפתחים ומפרסים את האפליקציה, צריך לוודא שכל משתני הסביבה הנדרשים מוגדרים. עוברים ל-Cloud Shell Terminal ומבצעים את הפקודות הבאות:
PROJECT_ID=$(gcloud config get-value project)

export PROJECT_ID $PROJECT_ID

export GOOGLE_API_KEY <YOUR API KEY that you saved>
  1. פיתוח גרסת build והרצה של האפליקציה באופן מקומי:

מוודאים שנמצאים בספריית הפרויקט ומריצים את הפקודות הבאות:

mvn package

mvn spring-boot:run 
  1. פריסה ב-Cloud Run
gcloud run deploy --source .

12. הסבר על פרטי ה-AI הגנרטיבי

אין צורך בפעולה נוספת. לידיעתך:

עכשיו, אחרי שפרסמתם את האפליקציה, כדאי להקדיש רגע כדי להבין איך ביצענו את החיפוש (טקסט ותמונה) והיצירה.

  1. חיפוש וקטורים שמבוסס על טקסט של משתמשים:

הבעיה הזו כבר טופלה ב-Cloud Run Functions שפרסמתי בקטע 'הפעלת אפליקציית Vector Search באינטרנט'.

  1. חיפוש וקטור מבוסס העלאת תמונה:

במקום להקליד את ההקשר כטקסט, נניח שהמשתמש רוצה להעלות תמונה של צעצוע מוכר שהוא רוצה לחפש באמצעותו. המשתמשים יכולים להעלות תמונה של צעצוע שהם אוהבים ולקבל תכונות רלוונטיות.

אנחנו משתמשים במודל Gemini 2.0 Flash של Google, שמופעל באמצעות LangChain4j, כדי לנתח את התמונה ולחלץ את ההקשר הרלוונטי, כמו הצבע, החומר, הסוג וקבוצת הגיל של הצעצוע.

ב-5 שלבים בלבד, העברנו את הקלט של נתוני המשתמש במגוון מופעים ליצירת תוצאות תואמות באמצעות הפעלה של מודל שפה גדול באמצעות מסגרת קוד פתוח. כך עושים זאת:

package cloudcode.helloworld.web;

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

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

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

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

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

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

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

    Response<AiMessage> response = gemini.generate(
        UserMessage.from(
            ImageContent.from(image, "image/jpeg"),
            TextContent.from(
                "The picture has a toy in it. Describe the toy in the image in one line. Do not add any prefix or title to your description. Just describe that toy that you see in the image in one line, do not describe the surroundings and other objects around the toy in the image. If you do not see any toy in the image, send  response stating that no toy is found in the input image.")));
   
    // 5. Get the text from the response and send it back to the controller
    searchText = response.content().text().trim();
    System.out.println("searchText inside Geminicall: " + searchText);
    return searchText;
  }
}
  1. איך השתמשנו ב-Imagen 3 כדי ליצור צעצוע בהתאמה אישית על סמך בקשת משתמש באמצעות AI גנרטיבי.

לאחר מכן, Imagen 3 יוצר תמונה של הצעצוע שעוצב בהתאמה אישית, ומאפשר למשתמש לראות בבירור את היצירה שלו. כך עשינו זאת ב-5 שלבים בלבד:

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

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

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

חיזוי מחירים

בקטע הקודם שלמעלה, התייחסנו לאופן שבו Imagen יוצרת את התמונה של הצעצוע שהמשתמש רוצה לעצב בעצמו. כדי שהם יוכלו לקנות אותו, צריך להגדיר לו מחיר באפליקציה. השתמשנו בלוגיקה אינטואיטיבית כדי להגדיר מחיר לצעצוע בהתאמה אישית. הלוגיקה היא להשתמש במחיר הממוצע של 5 הצעצועים הכי דומים (מבחינת תיאור) לצעצוע שהמשתמש מתכנן.

חיזוי המחיר של הצעצוע שנוצר הוא חלק חשוב באפליקציה הזו, והשתמשנו בגישה מבוססת-סוכנות כדי ליצור אותו. חדש: ערכת הכלים של AI גנרטיבי למסדי נתונים.

13. ערכת הכלים של AI גנרטיבי למסדי נתונים

Gen AI Toolbox for Databases הוא שרת בקוד פתוח של Google שמאפשר ליצור בקלות כלים של בינה מלאכותית גנרטיבית ליצירת אינטראקציה עם מסדי נתונים. הוא מאפשר לפתח כלים בקלות, במהירות ובאופן מאובטח יותר, על ידי טיפול בבעיות מורכבות כמו מאגר חיבורים, אימות ועוד. הוא עוזר לכם ליצור כלים של AI גנרטיבי שמאפשרים לנציגים לגשת לנתונים במסד הנתונים שלכם.

כדי להגדיר את הכלי ולהפוך את האפליקציה לסוכנית, צריך לפעול לפי השלבים הבאים: קישור ל-Codelab של Toolbox

עכשיו האפליקציה יכולה להשתמש בנקודת הקצה הזו של פונקציית Cloud Run כדי לאכלס את המחיר יחד עם התוצאה שנוצרה על ידי Imagen עבור התמונה של הצעצוע בהתאמה אישית.

14. בדיקת אפליקציית האינטרנט

עכשיו, אחרי שפיתחתם ופרסמתם את כל הרכיבים של האפליקציה, היא מוכנה להצגה בענן. בודקים את האפליקציה בכל התרחישים. הנה קישור לסרטון שמראה מה צפוי:

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

כך נראה דף הנחיתה:

241db19e7176e93e.png

15. הסרת המשאבים

כדי להימנע מצבירת חיובים בחשבון Google Cloud על המשאבים שבהם השתמשתם במאמר הזה, יש לפעול לפי השלבים הבאים:

  1. נכנסים לדף Manage resources במסוף Google Cloud.
  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete.
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.

16. מזל טוב

מעולה! ביצעתם בהצלחה חיפוש לפי הקשר ויצירה של מודעות של צעצועים באמצעות AlloyDB,‏ pgvector,‏ Imagen ו-Gemini 2.0, תוך ניצול ספריות בקוד פתוח ליצירת שילובים חזקים. שילוב היכולות של AlloyDB, Vertex AI ו-Vector Search אפשר לנו להתקדם משמעותית ביצירת חיפושים לפי הקשר וחיפושים וקטורים שיהיו נגישים, יעילים ומבוססי-משמעות.