1. מבוא
בשיעור Codelab הזה נסביר איך ליצור סוכן AI אינטראקטיבי של בריסטה לבית קפה. שימוש בקוד פתוח של גוגל ערכת פיתוח סוכנים (ADK) וה- Gemini 3.5 Flash מודל, תיישם Retrieval-Augmented Generation (RAG) כדי לעגן את המלצות הסוכן במערך נתונים של תפריט מדומה. לבסוף, תעטפו את הסוכן בממשק משתמש של Streamlit ותפרסו אותו ב-Cloud Run.
הפעולות שתבצעו:
- יוצרים מקור נתונים של RAG (
menu.json) שמכיל פריטי קפה, תגים ואלרגנים. - בונים סוכן AI באמצעות ADK
LlmAgentומקשרים כלי Python לטעינת נתוני התפריט. - עוטפים את הסוכן באפליקציית צ'אט של Streamlit שמנהלת את היסטוריית השיחות.
- פורסים את אפליקציית Streamlit ב-Cloud Run באמצעות פריסה מבוססת-מקור.
- בדיקת העיגון של RAG והמודעות לאלרגנים.

הדרישות
- דפדפן אינטרנט כמו Chrome.
- פרויקט ב-Google Cloud שהחיוב בו מופעל.
- היכרות בסיסית עם Python.
שיעור ה-Codelab הזה מיועד למפתחים בכל הרמות, כולל מתחילים.
העלות המשוערת: פחות מ-1.00 $.
2. לפני שמתחילים
יצירת פרויקט ב-Google Cloud
- במסוף Google Cloud, בוחרים או יוצרים פרויקט בענן של Google.
- הקפידו לוודא שהחיוב מופעל בפרויקט שלכם ב-Cloud. איך מוודאים שהחיוב מופעל בפרויקט
הפעלת Cloud Shell
- לחץ על הפעל את Cloud Shell בחלק העליון של קונסולת Google Cloud.

- אימות האימות:

gcloud auth list
- מוודאים שהפרויקט הפעיל מוגדר:
gcloud config get project
אם מזהה הפרויקט המוצג אינו נכון או שלא הוגדר אף אחד, הפעל:
gcloud config set project <YOUR_PROJECT_ID>
הפעלת ממשקי ה-API
מריצים את הפקודה הבאה כדי להפעיל את כל ממשקי ה-API הנדרשים:
gcloud services enable \
run.googleapis.com \
aiplatform.googleapis.com \
cloudbuild.googleapis.com
3. הגדרת הפרויקט
בשלב הזה, מאתחלים את משתני הסביבה של הפרויקט ויוצרים ספריית עבודה לפרויקט.
- בסשן הפעיל של Cloud Shell, מאתחלים את משתני הסביבה של הפרויקט הבאים:
export PROJECT_ID=$(gcloud config get-value project)
הערה: השתמש באזור הקרוב ביותר
מוצאים את האזור הקרוב ביותר ומחליפים את insert-region-here בפקודה הבאה:
export REGION=[insert-region-here]
- יוצרים ספריית פרויקט חדשה בשם
coffee-barista-agentועוברים אליה:
mkdir coffee-barista-agent && cd coffee-barista-agent
4. צור את מקור הנתונים של תפריט מדומה
כדי להקפיד על דיוק המידע שמתקבל מ-AI Barista ולמנוע ממנו להמציא פריטים שלא קיימים, צריך ליצור מערך נתונים של תפריט מקומי. הסוכן יקרא את הקובץ הזה בזמן הריצה באמצעות כלי בהתאמה אישית.
- יוצרים את הקובץ
menu.jsonופותחים אותו ב-Cloud Shell Editor:
cloudshell edit menu.json
- הדבק את תוכן ה-JSON הבא בעורך ושמור את הקובץ:
[
{
"name": "Espresso Solo",
"description": "A single shot of rich, bold espresso.",
"price": 2.50,
"tags": ["strong", "hot", "dairy-free", "sugar-free"],
"allergens": []
},
{
"name": "Oat Milk Honey Latte",
"description": "Creamy steamed oat milk with espresso and a touch of honey.",
"price": 5.00,
"tags": ["sweet", "hot", "dairy-free"],
"allergens": []
},
{
"name": "Cold Brew Coffee",
"description": "Smooth, slow-steeped cold brew served over ice.",
"price": 4.00,
"tags": ["strong", "cold", "dairy-free", "sugar-free"],
"allergens": []
},
{
"name": "Seasonal Pumpkin Latte",
"description": "Spiced pumpkin sauce, espresso, and steamed milk, topped with whipped cream.",
"price": 5.50,
"tags": ["sweet", "hot", "seasonal"],
"allergens": ["dairy"]
},
{
"name": "Classic Croissant",
"description": "Flaky, buttery traditional French pastry.",
"price": 3.50,
"tags": ["bakery", "savory"],
"allergens": ["wheat", "dairy"]
},
{
"name": "Vegan Blueberry Muffin",
"description": "Soft, sweet muffin packed with real blueberries, entirely plant-based.",
"price": 3.75,
"tags": ["bakery", "sweet", "dairy-free", "vegan"],
"allergens": ["wheat"]
},
{
"name": "Nitro Cold Brew",
"description": "Cold brew infused with nitrogen for a super smooth, creamy head.",
"price": 4.50,
"tags": ["strong", "cold", "dairy-free", "sugar-free"],
"allergens": []
},
{
"name": "Iced Caramel Macchiato",
"description": "Chilled milk and vanilla syrup marked with espresso and caramel drizzle.",
"price": 5.25,
"tags": ["sweet", "cold"],
"allergens": ["dairy"]
}
]
- ודא שקובץ ה-JSON מעוצב כראוי:
cat menu.json | python3 -m json.tool > /dev/null && echo "Valid JSON!"
💬 דיון: קובץ JSON מקומי לעומת מסדי נתונים פעילים
למה אנחנו משתמשים בקובץ פשוט של menu.json במקום במסד נתונים פעיל?
כדי ליצור במהירות מדריך או אב טיפוס, קובץ JSON מקומי מאפשר לדלג על ההגדרה הראשונית של מסד הנתונים ועל המורכבות שלה. עם זאת, באפליקציית ייצור ארגונית בעולם האמיתי, צריך לקשר את הסוכן למסד נתונים מנוהל כמו Cloud Firestore, AlloyDB או Cloud SQL.
שימוש במסד נתונים פעיל מאפשר למנהלי בתי קפה להוסיף פריטים עונתיים, לעדכן מחירים או לשנות תגי אלרגנים באופן דינמי בלי לבנות מחדש את קובץ אימג' של קונטיינר או לפרוס מחדש את קוד האפליקציה. בהמשך ה-Codelab נשתמש במסד נתונים פעיל כשלב אופציונלי.
5. פיתוח סוכן ADK
עכשיו מתקינים את החבילות הנדרשות ויוצרים את לוגיקת הליבה של סוכן ADK. תגדירו כלי get_menu() ותעבירו אותו אל LlmAgent.
- יוצרים את הקובץ
requirements.txtופותחים אותו ב-Cloud Shell Editor:
cloudshell edit requirements.txt
- מדביקים את התלויות הבאות בעורך ושומרים את הקובץ:
google-adk==2.2.0
streamlit==1.56.0
- יוצרים את הקובץ
agent.pyופותחים אותו ב-Cloud Shell Editor:
cloudshell edit agent.py
- מדביקים את הקוד הבא ב-
agent.py:
# agent.py
import json
from google.adk.agents import LlmAgent
# [START get_menu]
def get_menu() -> str:
"""Retrieves the coffee shop menu from menu.json.
Returns:
str: A JSON string representing the list of menu items.
"""
try:
with open("menu.json", "r") as f:
menu_data = json.load(f)
return json.dumps(menu_data)
except Exception as e:
return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]
# Create the barista agent
barista_agent = LlmAgent(
name="barista_agent",
model="gemini-3.5-flash",
instruction="""You are a friendly barista at ☕ Coffee Shop.
Your job is to recommend drinks and pastries to customers based on their preferences.
Rules you MUST follow:
1. You must recommend items ONLY from the menu returned by get_menu().
2. Do NOT recommend or suggest any item that is not present in the menu.
3. If a user's preference is vague or unclear, ask exactly ONE friendly clarifying question to narrow down what they want (e.g., cold or hot, sweet or strong, coffee or pastry).
4. Be warm and welcoming, but remain professional.
5. Ground your recommendations in the actual tags, descriptions, and allergens listed in the menu (e.g., if a user is dairy-free, recommend ONLY items tagged 'dairy-free' or with no dairy allergens).
""",
tools=[get_menu]
)
from google.adk.apps import App
# Define the App object
app = App(
name="coffee_barista_app",
root_agent=barista_agent
)
- יוצרים את הקובץ
app.pyופותחים אותו ב-Cloud Shell Editor:
cloudshell edit app.py
- מדביקים את הקוד הבא ב-
app.py:
# app.py
import streamlit as st
import json
# Set page config for a premium look
st.set_page_config(
page_title="☕ Coffee Shop - Barista Bot",
page_icon="☕",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS to make the header sticky (adapts to light/dark themes)
st.markdown("""
<style>
div[data-testid="element-container"]:has(.header-container),
div.element-container:has(.header-container) {
position: sticky;
top: 2.875rem;
z-index: 999;
background-color: transparent;
padding-bottom: 10px;
}
</style>
""", unsafe_allow_html=True)
# App Header (using inline styles for the permanent coffee theme look)
st.markdown("""
<div class="header-container" style="text-align: center; padding: 20px; background: linear-gradient(135deg, #8B5E3C, #6F4E37); color: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
<h1 style="margin: 0; font-size: 2.5rem; font-weight: 700; color: white;">☕ ☕ Coffee Shop</h1>
<p style="margin: 5px 0 0 0; font-size: 1.1rem; opacity: 0.9; color: white;">Your friendly AI Barista is ready to help you find the perfect drink or pastry!</p>
</div>
""", unsafe_allow_html=True)
# Load Menu for the sidebar
# [START load_menu]
try:
with open("menu.json", "r") as f:
menu_items = json.load(f)
except Exception as e:
st.error(f"Error loading menu: {e}")
menu_items = []
# [END load_menu]
# Sidebar Menu & Configuration
with st.sidebar:
st.markdown("## ☕ Coffee Shop Menu")
st.markdown("Explore our offerings and ask the barista for recommendations.")
st.markdown("---")
for item in menu_items:
with st.container(border=True):
st.markdown(f"**{item['name']}** • **${item['price']:.2f}**")
st.caption(item['description'])
# Tags & Allergens as native badges
tags = " ".join([f"`{t}`" for t in item.get("tags", [])])
if tags:
st.markdown(tags)
allergens = ", ".join(item.get("allergens", []))
if allergens:
st.markdown(f"⚠️ *Allergens: {allergens}*")
# Chat Interface
if "session_id" not in st.session_state:
import uuid
st.session_state.session_id = str(uuid.uuid4())
if "runner" not in st.session_state:
from google.adk.runners import InMemoryRunner
from agent import app
st.session_state.runner = InMemoryRunner(app=app)
if "messages" not in st.session_state:
st.session_state.messages = [
{"role": "assistant", "content": "Welcome to ☕ Coffee Shop! What can I get started for you today?"}
]
# Display existing messages
for msg in st.session_state.messages:
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
# User Input
if prompt := st.chat_input("Ask for recommendations (e.g., 'What dairy-free pastries do you have?')"):
# Display user message
with st.chat_message("user"):
st.markdown(prompt)
st.session_state.messages.append({"role": "user", "content": prompt})
# Generate response
with st.chat_message("assistant"):
try:
import asyncio
# Run the ADK runner asynchronously using asyncio.run
async def fetch_response():
return await st.session_state.runner.run_debug(
prompt,
session_id=st.session_state.session_id
)
res_events = asyncio.run(fetch_response())
response_text = "".join([
part.text
for event in res_events
if event.content and event.content.parts
for part in event.content.parts
if part.text
])
st.markdown(response_text)
st.session_state.messages.append({"role": "assistant", "content": response_text})
except Exception as e:
st.error(f"Apologies, I ran into an error: {e}")
💬 דיון: פשרות מודל ויעילות אסימוני אחזור
למה צריך להשתמש בכלי פונקציה כדי לאחזר את התפריט במקום פשוט להדביק את כל הטקסט של התפריט בהוראות המערכת של הסוכן?
כלכלת טוקנים! הוספת 8 פריטים להודעה היא זולה, אבל מה אם בית הקפה יתרחב ל-500 פריטים, כולל מרכיבים בהתאמה אישית? הדבקה של מערכי נתונים גדולים ישירות בהנחיית המערכת מגדילה את כמות הטוקנים של ההנחיה, וכך מגדילה את עלויות העסקאות ואת זמן האחזור של תגובות ה-API בכל שאילתה.
באמצעות כלי ADK, הסוכן מבקש באופן דינמי לקרוא את התפריט רק כשצריך. ה-LLM מקבל רק את נתוני התפריט הרלוונטיים כהקשר, מה שממזער את גודל אסימון ההנחיה.
💬 דיון: מצב הזיכרון ומאגרי הייצור
האם היסטוריית הצ'אט שנשמרת ב-st.session_state של Streamlit נשמרת גם כשמשתמש סוגר את כרטיסיית הדפדפן?
לא, זה לא קורה. st.session_state מאוחסן לחלוטין בזיכרון וייחודי לחיבור הדפדפן הפעיל. אם משתמש ירענן את הדף או יסגור את הכרטיסייה, היסטוריית השיחה שלו עם הבריסטה תימחק.
באפליקציה בסביבת ייצור, צריך לחבר את מפעיל ה-ADK לבק-אנד של אחסון מתמיד כמו Cloud Firestore או Redis. ADK מספק אבסטרקציות שירות מובנות (כמו SessionService) המאפשרות שמירה וחידוש של היסטוריית צ'אטים בקלות בין טעינות מחדש של דפים ומכשירים שונים.
6. פריסת הסוכן ב-Cloud Run
תפרסו את אפליקציית Streamlit ישירות מקוד המקור באמצעות חבילות ה-buildpacks המובנות של Cloud Run. כדי לפעול לפי העקרון של הרשאות מינימליות, תיצרו ותפרסו באמצעות חשבון שירות מותאם אישית ייעודי במקום להשתמש בחשבון השירות המוגדר כברירת מחדל של Compute Engine.
- צור חשבון שירות ייעודי:
gcloud iam service-accounts create barista-agent-sa \
--description="Service account for Coffee Barista ADK agent on Cloud Run" \
--display-name="Barista Agent Service Account"
- מקצים את תפקיד המשתמש Gemini Enterprise Agent Platform (
roles/aiplatform.user) לחשבון השירות החדש:
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"
- פרוס את השירות באמצעות
gcloud run deploy, תוך העברת כתובת האימייל בחשבון השירות החדש דרך הדגל--service-account:
gcloud run deploy coffee-barista \
--source . \
--region $REGION \
--allow-unauthenticated \
--labels dev-tutorial=codelab-streamlit-rag-adk \
--command "/cnb/lifecycle/launcher" \
--args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
--service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--set-env-vars GOOGLE_GENAI_USE_ENTERPRISE=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global
- לאחר הפריסה, אתר את כתובת ה-URL של השירות בפלט הפקודה.
💬 דיון: פריסת קונטיינרים לעומת קוד מקור ואבטחת IAM
פרסנו ב-Cloud Run באמצעות gcloud run deploy –source בלי ליצור קובץ Dockerfile או קובץ Procfile. איך Cloud Run גילה איך לקמפל ולהריץ את אפליקציית Python שלנו?
Cloud Run משתמש ב-Buildpacks מאחורי הקלעים כדי לנתח את המאגר שלכם. לאחר זיהוי נוכחות של קבצי מקור של requirements.txt ו-Python, המנוע מבצע קומפילציה ואורזת אוטומטית של מיכל זמן ריצה של Python.
כתיבת Dockerfile מותאם אישית מעניקה לך שליטה מלאה על חבילות המערכת ושכבות הבסיס של המכולה שלך. Procfile היא דרך פשוטה יותר להצהיר על פקודת ההפעלה בלי להגדיר מאגר תגים באופן מלא. אבל עבור פריסות מהירות, פריסה מהמקור (--source) יעילה מאוד.
למה יצרנו חשבון שירות מותאם אישית barista-agent-sa במקום להשתמש בחשבון השירות שמוגדר כברירת מחדל של Compute Engine?
בטיחות במקום הראשון! כברירת מחדל, לחשבון השירות של Compute Engine יש הרשאות עריכה רחבות מאוד. הפעלת מיכל Cloud Run שלנו תחת חשבון השירות המוגדר כברירת מחדל פירושה שאם לאפליקציה שלנו יש באג אבטחה, תוקף עלול לקרוא, לכתוב או למחוק משאבים אחרים בפרויקט Google Cloud שלנו.
על ידי יצירת חשבון שירות ייעודי והקצאתו רק לתפקיד roles/aiplatform.user, אנו פועלים לפי העקרון של הרשאות מינימליות: לאפליקציה יש בדיוק את הגישה הדרושה לה כדי להתקשר ל-Gemini, ולא יותר.
7. בדיקת התנהגות ה-RAG
פתחו את כתובת ה-URL של שירות Cloud Run בדפדפן אינטרנט ושאלו את ה-AI Barista שאלות כדי לבדוק את האבטחה והאילוצים שלו.
- בקשה בתפריט: שאל: "המלץ על משהו חזק וחם." צפוי: הסוכן ממליץ על אספרסו.
- מלכודת מחוץ לתפריט: שאלו: "יש לכם מאצ'ה פרפוצ'ינו?" צפוי: הסוכן מסרב בנימוס ומסביר שזה לא בתפריט.
- בקשה עם התייחסות לאלרגנים: שאלה: "יש לי אי-סבילות ללקטוז, מה אפשר להזמין?" תשובה צפויה: הסוכן ימליץ רק על פריטים בתפריט שלא מכילים חלב (כמו לאטה עם חלב שיבולת שועל, אספרסו, קולד ברו). הוא לא ממליץ על קפוצ'ינו או על קרואסון.

8. אופציונלי: עיגון הסוכן ב-Firestore באמצעות חיפוש וקטורי
בתרחיש של סביבת ייצור, אחסון פריטי התפריט בקובץ menu.json מקומי הוא לא אידיאלי, כי כל שינוי בתפריט מחייב בנייה מחדש של קובץ האימג' של הקונטיינר ופריסה מחדש של שירות Cloud Run.
כדי להפוך את האפליקציה לדינמית ולניתנת להרחבה, אפשר להעביר את נתוני התפריט אל Cloud Firestore ולהשתמש בחיפוש וקטורי כדי לאחזר רק את הפריטים הרלוונטיים ביותר בתפריט על סמך דמיון סמנטי.

1. הפעלת Firestore API ואתחול מסד הנתונים
מריצים את הפקודות הבאות כדי להפעיל את Firestore API וליצור מסד נתונים של Firestore בשם coffee-menu במצב Native:
gcloud services enable firestore.googleapis.com
gcloud firestore databases create --database="coffee-menu" --location=$REGION
הערה: יכולות לעבור 1-2 דקות עד שההפעלה של ה-API תתעדכן. אם פקודת יצירת מסד הנתונים מבקשת ממך API [firestore.googleapis.com] not enabled on project... Would you like to enable and retry?, הקלד Y כדי להמשיך, או המתן דקה והפעל שוב את הפקודה.
2. הוספת נתונים ל-Firestore באמצעות נתוני התפריט
כדי להזרים במהירות את מסד הנתונים של Firestore שלך עם פריטי התפריט מקובץ menu.json שלך, תוכל להריץ סקריפט Python באופן מקומי ב-Cloud Shell.
- התקינו את ספריות הלקוחות של Firestore ו-GenAI באופן מקומי ב-Cloud Shell כדי להריץ את סקריפט הזריעה:
pip3 install google-cloud-firestore==2.27.0 google-genai==2.11.0
- יצירת סקריפט להפצת תוכן
seed.py:
cloudshell edit seed.py
- מדביקים את הקוד הבא ב-
seed.py:
# seed.py
import json
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector
db = firestore.Client(database="coffee-menu")
client = genai.Client(
vertexai=True,
project=os.environ.get("PROJECT_ID"),
location=os.environ.get("REGION", "us-central1")
)
with open("menu.json", "r") as f:
menu_items = json.load(f)
for item in menu_items:
# Use the name as the document ID
doc_id = item["name"].lower().replace(" ", "-")
# Generate text embedding using Gemini Enterprise Agent Platform text-embedding-005 model
text_to_embed = f"{item['name']}: {item['description']}"
response = client.models.embed_content(
model="text-embedding-005",
contents=text_to_embed,
)
embedding = response.embeddings[0].values
# Add embedding vector to the menu item data
item["embedding"] = Vector(embedding)
db.collection("menu").document(doc_id).set(item)
print("Firestore menu collection seeded with vector embeddings successfully!")
- מריצים את הסקריפט:
python3 seed.py
3. יצירת אינדקס וקטורי ב-Firestore
כדי לבצע חיפושים וקטוריים בפריטים בתפריט, צריך ליצור אינדקס וקטורי מורכב בשדה embedding במסד הנתונים של Firestore.
הפעל את הפקודה הבאה במסוף Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=menu \
--query-scope=COLLECTION \
--database="coffee-menu" \
--field-config=field-path=embedding,vector-config='{"dimension":"768", "flat": "{}"}'
הערה: יצירת אינדקס ב-Firestore מתבצעת ברקע ויכולה להימשך כמה דקות. אפשר להמשיך לשלבים הבאים של ה-codelab בזמן שהאינדקס נוצר.
4. הענקת גישה ל-Firestore לחשבון השירות
כדי ששירות Cloud Run שלך יבקש שאילתה ל-Firestore, עליך להעניק לחשבון השירות שלו את התפקיד Cloud Datastore User (roles/datastore.user):
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/datastore.user"
הערה: למרות שאנחנו משתמשים ב-Cloud Firestore במצב Native, Google Cloud משתמשת בתפקידי IAM המאוחדים של Cloud Datastore (roles/datastore.viewer או roles/datastore.user) כדי לנהל את בקרת הגישה.
5. עדכון הקוד
כעת, עדכן את הקוד שלך כדי לאחזר את התפריט מ-Firestore במקום לקרוא אותו מ-menu.json.
- פותחים את
requirements.txtב-Cloud Shell Editor:
cloudshell edit requirements.txt
- מוסיפים את ספריות הלקוח של Firestore ו-GenAI לסוף הקובץ ושומרים אותו:
google-cloud-firestore==2.27.0
google-genai==2.11.0
- פותחים את
agent.pyב-Cloud Shell Editor:
cloudshell edit agent.py
- אתר את הבלוק
# [START get_menu]ב-agent.pyוהחלף אותו לחלוטין (מ-# [START get_menu]ל-# [END get_menu]) במימוש Firestore הבא:
# [START get_menu]
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector
def get_menu(query: str) -> str:
"""Retrieves coffee shop menu items matching the user's query.
Args:
query: The search query or preference to find matching menu items.
Returns:
str: A JSON string representing the list of top matching menu items.
"""
try:
# Initialize clients
db = firestore.Client(database="coffee-menu")
client = genai.Client()
# Generate embedding for the search query
response = client.models.embed_content(
model="text-embedding-005",
contents=query,
)
query_vector = response.embeddings[0].values
# Search the Firestore database using Vector Search
results = db.collection("menu").find_nearest(
vector_field="embedding",
query_vector=Vector(query_vector),
distance_measure=DistanceMeasure.COSINE,
limit=3,
).stream()
menu_data = []
for doc in results:
item = doc.to_dict()
# Remove embedding field to save tokens
item.pop("embedding", None)
menu_data.append(item)
return json.dumps(menu_data)
except Exception as e:
return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]
- פותחים את
app.pyב-Cloud Shell Editor:
cloudshell edit app.py
- מאתרים את הבלוק
# [START load_menu]ב-app.pyומחליפים אותו לגמרי (מ-# [START load_menu]עד# [END load_menu]) בלוגיקת הטעינה הבאה של Firestore:
# [START load_menu]
from google.cloud import firestore
try:
db = firestore.Client(database="coffee-menu")
docs = db.collection("menu").stream()
menu_items = []
for doc in docs:
item = doc.to_dict()
item.pop("embedding", None)
menu_items.append(item)
except Exception as e:
st.error(f"Error loading menu from Firestore: {e}")
menu_items = []
# [END load_menu]
6. פריסה מחדש ל-Cloud Run
פורסים את האפליקציה המעודכנת:
gcloud run deploy coffee-barista \
--source . \
--region $REGION \
--allow-unauthenticated \
--labels dev-tutorial=codelab-streamlit-rag-adk \
--command "/cnb/lifecycle/launcher" \
--args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
--service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--set-env-vars GOOGLE_GENAI_USE_ENTERPRISE=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global
7. אימות השילוב של Firestore
כדי לבדוק את החיבור של הסוכן ל-Firestore, הוסף פריט תפריט חדש ישירות ב-Firestore וודא שהסוכן ממליץ עליו.
- כדי לכתוב מסמך חדש לאוסף
menuב-Firestore באמצעות Python, מריצים את הפקודה הבאה ב-Cloud Shell:
python3 -c "
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector
db = firestore.Client(database='coffee-menu')
client = genai.Client(
vertexai=True,
project=os.environ.get('PROJECT_ID'),
location=os.environ.get('REGION', 'us-central1')
)
name = 'Matcha Green Tea Latte'
desc = 'Creamy steamed oat milk infused with premium Japanese matcha powder.'
res = client.models.embed_content(
model='text-embedding-005',
contents=f'{name}: {desc}'
)
embedding = res.embeddings[0].values
db.collection('menu').document('matcha-latte').set({
'name': name,
'description': desc,
'price': 5.50,
'tags': ['sweet', 'hot', 'dairy-free'],
'allergens': [],
'embedding': Vector(embedding)
})
print('Successfully added Matcha Latte with vector embeddings!')
"
- כדי לנקות את סשן הצ'אט ולטעון את מצב מסד הנתונים החדש, מרעננים את אפליקציית Streamlit בדפדפן.
- שימו לב ש:
- Matcha Green Tea Latte מופיע אוטומטית בתפריט שבסרגל הצד.
- שואלים את הצ'אט בוט: "יש לך משקאות מאצ'ה?"
- הסוכן צריך להמליץ על מאצ'ה לאטה עם התיאור והמחיר שהוספתם. כך אפשר לוודא שהסוכן מבוסס על שאילתה ישירות במסד הנתונים הפעיל של Firestore.
9. הסרת המשאבים
כדי למנוע חיובים שוטפים בחשבון לחיוב ב-Google Cloud, צריך למחוק את שירות Cloud Run שנפרס ואת חשבון השירות המותאם אישית.
מוחקים את שירות Cloud Run:
gcloud run services delete coffee-barista --region $REGION --quiet
מוחקים את חשבון השירות המותאם אישית:
gcloud iam service-accounts delete barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com --quiet
(אופציונלי) מוחקים את מסד הנתונים של Firestore (אם הוא נוצר):
gcloud firestore databases delete --database="coffee-menu" --quiet
שלב אופציונלי: מחיקת הפרויקט כולו. ⚠️ מבצעים את הפעולה הזו רק אם יצרתם פרויקט ייעודי לשיעור ה-Lab הזה
gcloud projects delete $PROJECT_ID
10. מזל טוב
מעולה! יצרתם ופרסתם סוכן AI של בריסטה מסוג Retrieval-Augmented Generation (יצירה משולבת-אחזור, RAG) באמצעות ADK ו-Cloud Run של Google.
מה למדתם
- יצירת כלים פשוטים של RAG ב-Python.
- שימוש ב-ADK
LlmAgentוב-InMemoryRunner. - יצירת חוויות צ'אט עם שמירת מצב ב-Streamlit.
- פריסת Streamlit ב-Cloud Run באמצעות בנייה על סמך קוד המקור.