1. Wprowadzenie
W tym ćwiczeniu w Codelabs utworzysz interaktywnego agenta AI Barista dla kawiarni. Korzystając z pakietu Agent Development Kit (ADK) od Google o otwartym kodzie źródłowym i modelu Gemini 3.5 Flash, zaimplementujesz generowanie wspomagane wyszukiwaniem (RAG), aby rekomendacje agenta były oparte na przykładowym zbiorze danych menu. Na koniec umieścisz agenta w interfejsie użytkownika Streamlit i wdrożysz go w Cloud Run.
Jakie zadania wykonasz
- Utwórz źródło danych RAG (
menu.json) zawierające produkty z kawą, tagi i alergeny. - Utwórz agenta AI za pomocą pakietu ADK
LlmAgenti połącz narzędzie Python, aby wczytać dane menu. - Umieść agenta w aplikacji do czatu Streamlit, która zarządza historią rozmów.
- Wdróż aplikację Streamlit w Cloud Run za pomocą wdrożenia opartego na źródle.
- Sprawdź powiązanie RAG i świadomość alergenów.

Czego potrzebujesz
- przeglądarka, np. Chrome;
- projekt Google Cloud z włączonymi płatnościami;
- podstawowa znajomość Pythona,
To ćwiczenie jest przeznaczone dla deweloperów na wszystkich poziomach zaawansowania, w tym dla początkujących.
Szacowany koszt: poniżej 1 USD.
2. Zanim zaczniesz
Tworzenie projektu Google Cloud
- W konsoli Google Cloud wybierz lub utwórz projekt w chmurze Google Cloud.
- Sprawdź, czy w projekcie Cloud włączone są płatności.
Uruchamianie Cloud Shell
- U góry konsoli Google Cloud kliknij Aktywuj Cloud Shell.

- Potwierdź uwierzytelnianie:

gcloud auth list
- Sprawdź, czy aktywny projekt jest ustawiony:
gcloud config get project
Jeśli wyświetlany identyfikator projektu jest nieprawidłowy lub nie jest ustawiony, uruchom to polecenie:
gcloud config set project <YOUR_PROJECT_ID>
Włącz interfejsy API
Aby włączyć wszystkie wymagane interfejsy API, uruchom to polecenie:
gcloud services enable \
run.googleapis.com \
aiplatform.googleapis.com \
cloudbuild.googleapis.com
3. Konfigurowanie projektu
W tym kroku zainicjujesz zmienne środowiskowe projektu i utworzysz katalog roboczy projektu.
- W aktywnej sesji Cloud Shell zainicjuj te zmienne środowiskowe projektu:
export PROJECT_ID=$(gcloud config get-value project)
Uwaga: użyj najbliższego regionu
Znajdź najbliższy region i zastąp nim ciąg insert-region-here w tym poleceniu:
export REGION=[insert-region-here]
- Utwórz nowy katalog projektu o nazwie
coffee-barista-agenti przejdź do niego:
mkdir coffee-barista-agent && cd coffee-barista-agent
4. Tworzenie źródła danych z przykładowym menu
Aby uziemić AI Baristę i zapobiec jej halucynacjom dotyczącym nieistniejących produktów, utworzysz lokalny zbiór danych menu. Agent odczyta ten plik w czasie działania za pomocą niestandardowego narzędzia.
- Utwórz i otwórz plik
menu.jsonw edytorze Cloud Shell:
cloudshell edit menu.json
- Wklej do edytora ten kod JSON i zapisz plik:
[
{
"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"]
}
]
- Sprawdź, czy plik JSON jest prawidłowo sformatowany:
cat menu.json | python3 -m json.tool > /dev/null && echo "Valid JSON!"
💬 Dyskusja: lokalny plik JSON a bazy danych na żywo
Dlaczego używamy prostego lokalnego pliku menu.json zamiast bazy danych na żywo?
W przypadku szybkiego samouczka lub prototypu lokalny plik JSON eliminuje początkowy czas i złożoność konfiguracji bazy danych. W rzeczywistej aplikacji produkcyjnej dla przedsiębiorstw połączysz jednak agenta z zarządzaną bazą danych, taką jak Cloud Firestore, AlloyDB lub Cloud SQL.
Korzystanie z bazy danych na żywo umożliwia menedżerom kawiarni dodawanie produktów sezonowych, aktualizowanie cen i dynamiczne dostosowywanie tagów alergenów bez ponownego tworzenia obrazu kontenera ani ponownego wdrażania kodu aplikacji. W dalszej części tego ćwiczenia użyjemy bazy danych na żywo jako kroku opcjonalnego.
5. Tworzenie agenta ADK
Teraz zainstalujesz wymagane pakiety i utworzysz podstawową logikę agenta ADK. Zdefiniujesz get_menu() i przekażesz je do LlmAgent.
- Utwórz i otwórz plik
requirements.txtw edytorze Cloud Shell:
cloudshell edit requirements.txt
- Wklej do edytora te zależności i zapisz plik:
google-adk==2.2.0
streamlit==1.58.0
- Utwórz i otwórz plik
agent.pyw edytorze Cloud Shell:
cloudshell edit agent.py
- Wklej do pliku
agent.pyten kod:
# 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
)
- Utwórz i otwórz plik
app.pyw edytorze Cloud Shell:
cloudshell edit app.py
- Wklej do pliku
app.pyten kod:
# 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}")
💬 Dyskusja: kompromisy dotyczące modelu i wydajność tokenów pobierania
Dlaczego w celu pobrania menu wywołujesz narzędzie funkcji, zamiast po prostu wkleić cały tekst menu do instrukcji systemowych agenta?
Ekonomia tokenów! Umieszczenie w prompcie 8 produktów jest tanie, ale co się stanie, jeśli kawiarnia rozszerzy ofertę do 500 produktów, w tym składników niestandardowych? Wklejanie dużych zbiorów danych bezpośrednio w prompcie systemowym zwiększa liczbę tokenów promptu, co podnosi koszty transakcji i wydłuża czas oczekiwania na odpowiedź interfejsu API w przypadku każdego zapytania.
Dzięki narzędziu ADK agent dynamicznie prosi o odczytanie menu TYLKO wtedy, gdy jest to potrzebne. Model LLM otrzymuje tylko odpowiednie dane menu jako kontekst, co minimalizuje rozmiar tokena promptu.
💬 Dyskusja: stan pamięci i sklepy produkcyjne
Czy historia czatu przechowywana w st.session_state w Streamlit jest zachowywana, gdy użytkownik zamknie kartę przeglądarki?
Nie, nie ma. st.session_state jest przechowywany w pamięci i unikalny dla aktywnego połączenia przeglądarki. Jeśli użytkownik odświeży stronę lub zamknie kartę, historia rozmowy z baristą zostanie utracona.
W przypadku aplikacji produkcyjnej połączysz moduł wykonawczy ADK z trwałym backendem pamięci masowej, takim jak Cloud Firestore lub Redis. ADK udostępnia wbudowane abstrakcje usług (np. SessionService), które ułatwiają zapisywanie i wznawianie historii czatu po ponownym załadowaniu strony i na różnych urządzeniach.
6. Wdrażanie agenta w Cloud Run
Wdrożysz aplikację Streamlit bezpośrednio ze źródła za pomocą wbudowanych pakietów kompilacji Cloud Run. Aby postępować zgodnie z zasadą jak najmniejszych uprawnień, utwórz i wdróż, używając dedykowanego niestandardowego konta usługi, zamiast domyślnego konta usługi Compute Engine.
- Utwórz dedykowane konto usługi:
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"
- Przypisz do nowego konta usługi rolę użytkownika platformy agentów Gemini Enterprise (
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"
- Wdróż usługę za pomocą polecenia
gcloud run deploy, przekazując nowy adres e-mail konta usługi za pomocą flagi--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_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global
- Po wdrożeniu znajdź adres URL usługi w wyniku polecenia.
💬 Dyskusja: wdrażanie kontenerów a źródło i zabezpieczenia IAM
Wdrożyliśmy aplikację w Cloud Run za pomocą polecenia gcloud run deploy –source bez tworzenia pliku Dockerfile ani Procfile. Jak Cloud Run dowiedział się, jak skompilować i wykonać naszą aplikację w Pythonie?
Cloud Run używa w tle pakietów Buildpacks do analizowania repozytorium. Po wykryciu plików źródłowychrequirements.txt i Python silnik automatycznie kompiluje i pakuje kontener środowiska wykonawczego Pythona.
Pisanie niestandardowego Dockerfile daje pełną kontrolę nad pakietami systemowymi i warstwami bazowymi kontenera. Procfile to prostszy sposób na zadeklarowanie polecenia uruchamiania bez pełnej konfiguracji kontenera. W przypadku szybkich wdrożeń bardzo wydajne jest jednak wdrażanie z kodu źródłowego (--source).
Dlaczego wykonaliśmy dodatkowy krok polegający na utworzeniu niestandardowego konta usługi barista-agent-sa zamiast użyć domyślnego konta usługi Compute Engine?
Bezpieczeństwo przede wszystkim! Domyślne konto usługi Compute Engine ma domyślnie bardzo szerokie uprawnienia Edytującego. Uruchomienie kontenera Cloud Run na domyślnym koncie usługi oznacza, że jeśli w naszej aplikacji wystąpi błąd zabezpieczeń, osoba atakująca może potencjalnie odczytywać, zapisywać lub usuwać inne zasoby w naszym projekcie Google Cloud.
Tworząc dedykowane konto usługi i przypisując mu tylko rolę roles/aiplatform.user, postępujemy zgodnie z zasadą jak najmniejszych uprawnień: aplikacja ma dokładnie taki dostęp, jakiego potrzebuje do wywoływania Gemini, i nic więcej.
7. Testowanie działania RAG
Otwórz adres URL usługi Cloud Run w przeglądarce i zadaj pytania AI Barista, aby sprawdzić jej podstawy i ograniczenia bezpieczeństwa.
- Prośba w menu: zapytanie: „Poleć coś mocnego i ciepłego”. Oczekiwana odpowiedź: agent poleca espresso.
- Pułapka związana z produktami spoza menu: zapytaj: „Czy macie matcha frappuccino?” Oczekiwana odpowiedź: agent grzecznie odmawia i wyjaśnia, że nie ma tego w menu.
- Zapytanie uwzględniające alergeny: zapytaj: „Mam nietolerancję laktozy. Co mogę zamówić?” Oczekiwana odpowiedź: agent poleca tylko pozycje z menu bez nabiału (np. latte na mleku owsianym, espresso, cold brew). Nie poleca cappuccino ani rogalika.

8. Opcjonalnie: powiąż agenta z Firestore za pomocą wyszukiwania wektorowego
W środowisku produkcyjnym przechowywanie pozycji menu w lokalnym pliku menu.json nie jest idealne, ponieważ każda zmiana menu wymaga ponownego skompilowania obrazu kontenera i ponownego wdrożenia usługi Cloud Run.
Aby aplikacja była dynamiczna i skalowalna, możesz przenieść dane menu do Cloud Firestore i użyć wyszukiwania wektorowego, aby pobierać tylko najbardziej odpowiednie pozycje menu na podstawie podobieństwa semantycznego.

1. Włączanie interfejsu Firestore API i inicjowanie bazy danych
Aby włączyć interfejs Firestore API i utworzyć bazę danych Firestore o nazwie coffee-menu w trybie natywnym, uruchom te polecenia:
gcloud services enable firestore.googleapis.com
gcloud firestore databases create --database="coffee-menu" --location=$REGION
Uwaga: włączenie interfejsu API może potrwać 1–2 minuty. Jeśli polecenie tworzenia bazy danych wyświetli znak zachęty API [firestore.googleapis.com] not enabled on project... Would you like to enable and retry?, wpisz Y, aby kontynuować, lub poczekaj minutę i ponownie uruchom polecenie.
2. Wypełnianie Firestore danymi menu
Aby szybko wypełnić bazę danych Firestore elementami menu z pliku menu.json, możesz uruchomić lokalnie w Cloud Shell skrypt w Pythonie.
- Aby uruchomić skrypt inicjujący, zainstaluj lokalnie w Cloud Shell biblioteki klienta Firestore i GenAI:
pip3 install google-cloud-firestore==2.27.0 google-genai==2.11.0
- Utwórz skrypt początkowy
seed.py:
cloudshell edit seed.py
- Wklej do pliku
seed.pyten kod:
# 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 Vertex AI text-embedding-004 model
text_to_embed = f"{item['name']}: {item['description']}"
response = client.models.embed_content(
model="text-embedding-004",
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!")
- Uruchom skrypt:
python3 seed.py
3. Tworzenie indeksu wektorowego Firestore
Aby przeprowadzać wyszukiwania wektorowe w przypadku pozycji menu, musisz utworzyć złożony indeks wektorowy w polu embedding w bazie danych Firestore.
Uruchom w terminalu Cloud Shell to polecenie:
gcloud firestore indexes composite create \
--collection-group=menu \
--query-scope=COLLECTION \
--database="coffee-menu" \
--field-config=field-path=embedding,vector-config='{"dimension":"768", "flat": "{}"}'
Uwaga: tworzenie indeksu Firestore odbywa się w tle i może potrwać kilka minut. Podczas tworzenia indeksu możesz przejść do kolejnych kroków w tym samouczku.
4. Przyznawanie kontu usługi dostępu do Firestore
Aby usługa Cloud Run mogła wysyłać zapytania do Firestore, musisz przyznać jej kontu usługi rolę Użytkownik Cloud Datastore (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"
Uwaga: mimo że używamy Cloud Firestore w trybie natywnym, Google Cloud wykorzystuje ujednolicone role IAM Cloud Datastore (roles/datastore.viewer lub roles/datastore.user) do zarządzania kontrolą dostępu.
5. Aktualizowanie kodu
Teraz zaktualizuj kod, aby pobierać menu z Firestore zamiast odczytywać je z menu.json.
- Otwórz plik
requirements.txtw edytorze Cloud Shell:
cloudshell edit requirements.txt
- Dodaj biblioteki klienta Firestore i GenAI na końcu pliku i zapisz go:
google-cloud-firestore==2.27.0
google-genai==2.11.0
- Otwórz plik
agent.pyw edytorze Cloud Shell:
cloudshell edit agent.py
- Znajdź blok
# [START get_menu]wagent.pyi zastąp go w całości (od# [START get_menu]do# [END get_menu]) tą implementacją 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-004",
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]
- Otwórz plik
app.pyw edytorze Cloud Shell:
cloudshell edit app.py
- Znajdź blok
# [START load_menu]w plikuapp.pyi całkowicie go zastąp (od# [START load_menu]do# [END load_menu]) tym kodem logiki wczytywania 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. Ponowne wdrażanie w Cloud Run
Wdróż zaktualizowaną aplikację:
gcloud run deploy coffee-barista \
--source . \
--region $REGION \
--allow-unauthenticated \
--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_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global
7. Weryfikowanie integracji z Firestore
Aby przetestować połączenie agenta z Firestore, dodaj zupełnie nowy element menu bezpośrednio w Firestore i sprawdź, czy agent go rekomenduje.
- Uruchom w Cloud Shell to polecenie, aby zapisać nowy dokument w kolekcji
menuw Firestore za pomocą Pythona:
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 milk infused with premium Japanese matcha powder.'
res = client.models.embed_content(
model='text-embedding-004',
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!')
"
- Odśwież aplikację Streamlit w przeglądarce, aby wyczyścić sesję czatu i wczytać nowy stan bazy danych.
- Zwróć uwagę na to, że:
- W menu na pasku bocznym automatycznie pojawi się Matcha Green Tea Latte.
- Zapytaj czatbota: „Czy masz jakieś napoje z matchą?”
- Agent powinien skutecznie polecić nową Matcha Green Tea Latte z opisem i ceną, które właśnie dodano. Potwierdza to, że agent wysyła zapytania bezpośrednio do Twojej aktywnej bazy danych Firestore.
9. Czyszczenie danych
Aby uniknąć obciążenia konta rozliczeniowego Google Cloud bieżącymi opłatami, usuń wdrożoną usługę Cloud Run i niestandardowe konto usługi.
Usuń usługę Cloud Run:
gcloud run services delete coffee-barista --region $REGION --quiet
Usuń niestandardowe konto usługi:
gcloud iam service-accounts delete barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com --quiet
(Opcjonalnie) Usuń bazę danych Firestore (jeśli została utworzona):
gcloud firestore databases delete --database="coffee-menu" --quiet
Opcjonalnie: usuń cały projekt. ⚠️ Zrób to TYLKO wtedy, gdy masz projekt utworzony specjalnie na potrzeby tego laboratorium.
gcloud projects delete $PROJECT_ID
10. Gratulacje
Gratulacje! Masz utworzonego i wdrożonego agenta AI Barista opartego na technice generowania wspomaganego wyszukiwaniem (RAG) przy użyciu pakietu ADK od Google i Cloud Run.
Czego się nauczysz
- Tworzenie prostych narzędzi RAG w Pythonie.
- Korzystanie z pakietu ADK
LlmAgentiInMemoryRunner. - Tworzenie czatów z zachowywaniem stanu w Streamlit.
- Wdrażanie Streamlit w Cloud Run za pomocą kompilacji opartych na źródłach.