1. Einführung
In diesem Codelab erstellen Sie einen interaktiven KI-Barista-Agenten für ein Café. Mit dem Open-Source-Agent Development Kit (ADK) von Google und dem Modell Gemini 3.5 Flash implementieren Sie Retrieval-Augmented Generation (RAG), um die Empfehlungen des Agents auf einem Mock-Menü-Dataset zu basieren. Schließlich verpacken Sie den Agent in einer Streamlit-Benutzeroberfläche und stellen ihn in Cloud Run bereit.
Aufgaben
- Erstellen Sie eine RAG-Datenquelle (
menu.json) mit Kaffeeprodukten, Tags und Allergenen. - Erstellen Sie einen KI-Agenten mit dem ADK
LlmAgentund verbinden Sie ein Python-Tool, um die Menüdaten zu laden. - Den Agenten in eine Streamlit-Chatanwendung einbinden, die den Unterhaltungsverlauf verwaltet.
- Streamlit-App mit quellcodebasierter Bereitstellung in Cloud Run bereitstellen
- RAG-Fundierung und Allergenbewusstsein testen

Voraussetzungen
- Ein Webbrowser wie Chrome.
- Google Cloud-Projekt mit aktivierter Abrechnungsfunktion.
- Grundkenntnisse in Python.
Dieses Codelab richtet sich an Entwickler aller Erfahrungsstufen, auch an Anfänger.
Geschätzte Kosten: Weniger als 1,00 USD.
2. Hinweis
Google Cloud-Projekt erstellen
- In der Google Cloud Console, wählen Sie ein Google Cloud-Projekt aus oder erstellen Sie eines.
- Die Abrechnung für das Cloud-Projekt muss aktiviert sein. So prüfen Sie, ob die Abrechnung für ein Projekt aktiviert ist.
Cloud Shell starten
- Klicken Sie oben in der Google Cloud Console auf Cloud Shell aktivieren.

- Authentifizierung überprüfen:

gcloud auth list
- Prüfen Sie, ob Ihr aktives Projekt festgelegt ist:
gcloud config get project
Wenn die angezeigte Projekt-ID nicht korrekt ist oder keine festgelegt ist, führen Sie Folgendes aus:
gcloud config set project <YOUR_PROJECT_ID>
APIs aktivieren
Führen Sie diesen Befehl aus, um alle erforderlichen APIs zu aktivieren:
gcloud services enable \
run.googleapis.com \
aiplatform.googleapis.com \
cloudbuild.googleapis.com
3. Projekt einrichten
In diesem Schritt initialisieren Sie die Umgebungsvariablen Ihres Projekts und erstellen ein Arbeitsverzeichnis für Ihr Projekt.
- Initialisieren Sie in Ihrer aktiven Cloud Shell-Sitzung die folgenden Projektumgebungsvariablen:
export PROJECT_ID=$(gcloud config get-value project)
Hinweis: Verwenden Sie die nächstgelegene Region.
Suchen Sie nach der Region, die Ihnen am nächsten liegt, und ersetzen Sie „insert-region-here“ im folgenden Befehl durch diese Region:
export REGION=[insert-region-here]
- Erstellen Sie ein neues Projektverzeichnis mit dem Namen
coffee-barista-agentund wechseln Sie dorthin:
mkdir coffee-barista-agent && cd coffee-barista-agent
4. Mock-Menü-Datenquelle erstellen
Um den KI-Barista zu fundieren und zu verhindern, dass er nicht vorhandene Artikel halluziniert, erstellen Sie ein lokales Menü-Dataset. Der KI-Agent liest diese Datei zur Laufzeit über ein benutzerdefiniertes Tool.
- Erstellen Sie
menu.jsonim Cloud Shell-Editor und öffnen Sie die Datei:
cloudshell edit menu.json
- Fügen Sie den folgenden JSON-Inhalt in den Editor ein und speichern Sie die Datei:
[
{
"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"]
}
]
- Überprüfen Sie, ob die JSON-Datei korrekt formatiert ist:
cat menu.json | python3 -m json.tool > /dev/null && echo "Valid JSON!"
💬 Diskussion: Lokale JSON-Daten im Vergleich zu Live-Datenbanken
Warum verwenden wir eine einfache lokale Datei „menu.json“ anstelle einer Live-Datenbank?
Für ein schnelles Tutorial oder einen Prototyp entfällt durch die Verwendung einer lokalen JSON-Datei der anfängliche Zeitaufwand und die Komplexität der Datenbankeinrichtung. In einer echten Produktionsanwendung für Unternehmen würden Sie den Agent jedoch mit einer verwalteten Datenbank wie Cloud Firestore, AlloyDB oder Cloud SQL verbinden.
Durch die Verwendung einer Live-Datenbank können Café-Manager saisonale Artikel hinzufügen, Preise aktualisieren oder Allergenkennzeichnungen dynamisch anpassen, ohne das Container-Image neu erstellen oder den Anwendungscode neu bereitstellen zu müssen. Wir verwenden später im Codelab eine Live-Datenbank als optionalen Schritt.
5. ADK-KI-Agenten erstellen
Jetzt installieren Sie die erforderlichen Pakete und erstellen die zentrale ADK-Agent-Logik. Sie definieren ein get_menu()-Tool und übergeben es an ein LlmAgent.
- Erstellen Sie
requirements.txtim Cloud Shell-Editor und öffnen Sie die Datei:
cloudshell edit requirements.txt
- Fügen Sie die folgenden Abhängigkeiten in den Editor ein und speichern Sie die Datei:
google-adk==2.2.0
streamlit==1.56.0
- Erstellen Sie
agent.pyim Cloud Shell-Editor und öffnen Sie die Datei:
cloudshell edit agent.py
- Fügen Sie den folgenden Code in
agent.pyein:
# 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
)
- Erstellen Sie
app.pyim Cloud Shell-Editor und öffnen Sie die Datei:
cloudshell edit app.py
- Fügen Sie den folgenden Code in
app.pyein:
# 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}")
💬 Diskussion: Modell-Kompromisse & Effizienz der Token-Abfrage
Warum sollte ein Funktionstool aufgerufen werden, um das Menü abzurufen, anstatt den gesamten Menütext einfach in die Systemanweisungen des Agenten einzufügen?
Token-Ökonomie! Acht Elemente im Prompt sind günstig, aber was passiert, wenn das Café auf 500 Artikel erweitert wird, einschließlich benutzerdefinierter Zutaten? Das direkte Einfügen großer Datensätze in die Systemeingabeaufforderung führt zu einer Erhöhung der Anzahl der Eingabeaufforderungstoken, was die Transaktionskosten und die Latenz der API-Antworten bei jeder einzelnen Abfrage erhöht.
Mithilfe eines ADK-Tools fragt der Agent dynamisch nur dann nach dem Menü, wenn dies erforderlich ist. Der LLM empfängt nur die relevanten Menüdaten als Kontext, wodurch die Größe des Eingabeaufforderungstokens minimiert wird.
💬 Diskussion: Speicherzustand & Produktionsspeicher
Bleibt der in Streamlits st.session_state gespeicherte Chatverlauf erhalten, wenn ein Benutzer seinen Browsertab schließt?
Nein. st.session_state befindet sich vollständig im Arbeitsspeicher und ist eindeutig der aktiven Browserverbindung zugeordnet. Wenn ein Benutzer die Seite aktualisiert oder den Tab schließt, geht sein Gesprächsverlauf mit dem Barista verloren.
Bei einer Produktionsanwendung würden Sie den ADK-Runner mit einem nichtflüchtigen Speicher-Backend wie Cloud Firestore oder Redis verbinden. ADK bietet integrierte Serviceabstraktionen (wie SessionService), die es einfach machen, den Chatverlauf über Seitenneuladungen und Geräte hinweg zu speichern und fortzusetzen.
6. Agent in Cloud Run bereitstellen
Sie werden die Streamlit-Anwendung direkt aus dem Quellcode mithilfe der in Cloud Run integrierten Buildpacks bereitstellen. Um dem Prinzip der geringsten Berechtigung zu folgen, werden Sie ein dediziertes benutzerdefiniertes Dienstkonto erstellen und damit bereitstellen, anstatt das standardmäßige Compute Engine-Dienstkonto zu verwenden.
- Dediziertes Dienstkonto erstellen:
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"
- Weisen Sie dem neuen Dienstkonto die Benutzerrolle „Gemini Enterprise Agent Platform“ (
roles/aiplatform.user) zu:
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"
- Stellen Sie den Dienst mit
gcloud run deploybereit und übergeben Sie die neue E-Mail-Adresse des Dienstkontos über das Flag--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
- Suchen Sie nach der Bereitstellung in der Befehlsausgabe nach der Dienst-URL.
💬 Diskussion: Container im Vergleich zu Quellcode bereitstellen und IAM-Sicherheit
Wir haben die Bereitstellung auf Cloud Run mit gcloud run deploy –source durchgeführt, ohne eine Dockerfile oder eine Procfile zu erstellen. Wie hat Cloud Run herausgefunden, wie unsere Python-App kompiliert und ausgeführt werden muss?
Cloud Run verwendet Buildpacks, um Ihr Repository zu analysieren. Wenn die Engine das Vorhandensein vonrequirements.txt und Python-Quelldateien erkennt, kompiliert und verpackt sie automatisch einen Python-Laufzeitcontainer.
Wenn Sie ein benutzerdefiniertes Dockerfile schreiben, haben Sie die vollständige Kontrolle über die Systempakete und Basisebenen Ihres Containers. Mit Procfile können Sie den Startbefehl einfacher deklarieren, ohne einen Container vollständig zu konfigurieren. Für schnelle Bereitstellungen ist die Bereitstellung aus der Quelle (--source) jedoch sehr effizient.
Warum haben wir den zusätzlichen Schritt zum Erstellen eines benutzerdefinierten Dienstkontos „barista-agent-sa“ durchlaufen, anstatt einfach das Compute Engine-Standarddienstkonto zu verwenden?
Sicherheit geht vor! Das standardmäßige Compute Engine-Dienstkonto hat standardmäßig sehr weitreichende Bearbeitungsberechtigungen. Wenn wir unseren Cloud Run-Container unter dem Standarddienstkonto ausführen, kann ein Angreifer, falls unsere App einen Sicherheitsfehler aufweist, möglicherweise andere Ressourcen in unserem Google Cloud-Projekt lesen, schreiben oder löschen.
Indem wir ein dediziertes Dienstkonto erstellen und ihm nur die Rolle roles/aiplatform.user zuweisen, folgen wir dem Prinzip der geringsten Berechtigung: Die App hat genau den Zugriff, den sie zum Aufrufen von Gemini benötigt, und nichts weiter.
7. RAG-Verhalten testen
Öffnen Sie die Cloud Run-Dienst-URL in einem Webbrowser und stellen Sie dem KI-Barista Fragen, um seine Fundierung und Sicherheitsbeschränkungen zu testen.
- Menüanfrage: Frage: "Empfehlen Sie etwas Starkes und Warmes."Erwartet: Der Mitarbeiter empfiehlt Espresso.
- Falle: Frage: „Haben Sie einen Matcha-Frappuccino?“ Erwartet:Der Agent lehnt höflich ab und erklärt, dass er nicht auf der Speisekarte steht.
- Anfrage mit Angabe von Allergien:Frage: „Ich habe eine Laktoseintoleranz. Was kann ich bestellen?“ Erwartet:Der Kundenservicemitarbeiter empfiehlt nur laktosefreie Menüpunkte (z. B. Hafermilch-Latte, Espresso, Cold Brew). Es empfiehlt weder Cappuccino noch Croissant.

8. Optional: Binden Sie Ihren Agenten mithilfe der Vektorsuche in Firestore ein.
In einem Produktionsszenario ist das Speichern der Menüpunkte in einer lokalen menu.json-Datei nicht optimal, da jede Änderung am Menü das Neuerstellen des Container-Images und die erneute Bereitstellung des Cloud Run-Dienstes erfordert.
Um die Anwendung dynamisch und skalierbar zu gestalten, können Sie Ihre Menüdaten nach Cloud Firestore migrieren und Vektorsuche verwenden, um nur die relevantesten Menüpunkte basierend auf semantischer Ähnlichkeit abzurufen.

1. Firestore API aktivieren und Datenbank initialisieren
Führen Sie die folgenden Befehle aus, um die Firestore-API zu aktivieren und eine Firestore-Datenbank mit dem Namen coffee-menu im nativen Modus zu erstellen:
gcloud services enable firestore.googleapis.com
gcloud firestore databases create --database="coffee-menu" --location=$REGION
Hinweis: Die API-Aktivierung kann 1–2 Minuten dauern. Wenn Sie beim Befehl zur Datenbankerstellung mit API [firestore.googleapis.com] not enabled on project... Would you like to enable and retry? aufgefordert werden, geben Sie Y ein, um fortzufahren, oder warten Sie eine Minute und führen Sie den Befehl erneut aus.
2. Seed Firestore mit den Menüdaten
Um Ihre Firestore-Datenbank schnell mit den Menüpunkten aus Ihrer menu.json-Datei zu befüllen, können Sie ein Python-Skript lokal in Cloud Shell ausführen.
- Installieren Sie die Firestore- und GenAI-Clientbibliotheken lokal in Cloud Shell, um das Seeding-Script auszuführen:
pip3 install google-cloud-firestore==2.27.0 google-genai==2.11.0
- Erstellen Sie ein Seeding-Script
seed.py:
cloudshell edit seed.py
- Fügen Sie den folgenden Code in
seed.pyein:
# 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!")
- Führen Sie das Script aus:
python3 seed.py
3. Firestore-Vektorindex erstellen
Wenn Sie Vektorsuchen für Ihre Menüelemente durchführen möchten, müssen Sie einen zusammengesetzten Vektorindex für das Feld embedding in Ihrer Firestore-Datenbank erstellen.
Führen Sie im Cloud Shell-Terminal den folgenden Befehl aus:
gcloud firestore indexes composite create \
--collection-group=menu \
--query-scope=COLLECTION \
--database="coffee-menu" \
--field-config=field-path=embedding,vector-config='{"dimension":"768", "flat": "{}"}'
Hinweis: Die Erstellung des Firestore-Index erfolgt im Hintergrund und kann einige Minuten dauern. Sie können mit den nächsten Schritten des Codelabs fortfahren, während der Index erstellt wird.
4. Firestore Zugriff auf das Dienstkonto gewähren
Damit Ihr Cloud Run-Dienst Firestore abfragen kann, müssen Sie seinem Dienstkonto die Rolle Cloud Datastore-Nutzer (roles/datastore.user) zuweisen:
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/datastore.user"
Hinweis:Obwohl wir Cloud Firestore im nativen Modus verwenden, nutzt Google Cloud die einheitlichen Cloud Datastore-IAM-Rollen (roles/datastore.viewer oder roles/datastore.user) zur Verwaltung der Zugriffssteuerung.
5. Code aktualisieren
Aktualisieren Sie nun Ihren Code, um das Menü aus Firestore abzurufen, anstatt es aus menu.json zu lesen.
- Öffnen Sie
requirements.txtim Cloud Shell-Editor:
cloudshell edit requirements.txt
- Hängen Sie die Firestore- und GenAI-Clientbibliotheken an das Ende der Datei an und speichern Sie sie:
google-cloud-firestore==2.27.0
google-genai==2.11.0
- Öffnen Sie
agent.pyim Cloud Shell-Editor:
cloudshell edit agent.py
- Suchen Sie in
agent.pynach dem Block# [START get_menu]und ersetzen Sie ihn vollständig (von# [START get_menu]bis# [END get_menu]) durch die folgende Firestore-Implementierung:
# [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]
- Öffnen Sie
app.pyim Cloud Shell-Editor:
cloudshell edit app.py
- Suchen Sie in
app.pynach dem Block# [START load_menu]und ersetzen Sie ihn vollständig (von# [START load_menu]bis# [END load_menu]) durch die folgende Firestore-Ladelogik:
# [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. Nochmalige Bereitstellung in Cloud Run
Stellen Sie die aktualisierte Anwendung bereit:
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. Überprüfen Sie die Firestore-Integration.
Um die Verbindung des Agenten zu Firestore zu testen, fügen Sie direkt in Firestore einen neuen Menüpunkt hinzu und überprüfen Sie, ob der Agent diesen empfiehlt.
- Führen Sie den folgenden Befehl in Cloud Shell aus, um mit Python ein neues Dokument in die
menu-Sammlung in Firestore zu schreiben:
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!')
"
- Aktualisieren Sie Ihre Streamlit-App im Browser, um die Chatsitzung zu löschen und den neuen Datenbankstatus zu laden.
- Beachten Sie Folgendes:
- Matcha Green Tea Latte wird automatisch im Seitenleistenmenü angezeigt.
- Fragen Sie den Chatbot: „Do you have any matcha drinks?“ (Gibt es Matcha-Getränke?)
- Der Kundenservicemitarbeiter sollte den neuen Matcha Green Tea Latte mit der Beschreibung und dem Preis, die Sie gerade hinzugefügt haben, empfehlen. Dies bestätigt, dass der Agent direkt in Ihrer Live-Firestore-Datenbank abgefragt wird!
9. Bereinigen
Damit Ihrem Google Cloud-Rechnungskonto keine laufenden Gebühren in Rechnung gestellt werden, löschen Sie den bereitgestellten Cloud Run-Dienst und das benutzerdefinierte Dienstkonto.
Löschen Sie den Cloud Run-Dienst:
gcloud run services delete coffee-barista --region $REGION --quiet
Löschen Sie das benutzerdefinierte Dienstkonto:
gcloud iam service-accounts delete barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com --quiet
(Optional) Löschen Sie die Firestore-Datenbank (falls erstellt):
gcloud firestore databases delete --database="coffee-menu" --quiet
Optionaler Schritt: Löschen Sie das gesamte Projekt. ⚠️ Führen Sie diesen Schritt NUR aus, wenn Sie für dieses Lab ein eigenes Projekt erstellt haben.
gcloud projects delete $PROJECT_ID
10. Glückwunsch
Glückwunsch! Sie haben einen RAG-KI-Barista-Agenten (Retrieval Augmented Generation) mit dem ADK von Google und Cloud Run erstellt und bereitgestellt.
Das haben Sie gelernt
- Einfache RAG-Tools in Python erstellen
- Verwendung des ADK
LlmAgentundInMemoryRunner. - Erstellung zustandsbehafteter Chat-Erlebnisse in Streamlit.
- Streamlit mit quellbasierten Builds in Cloud Run bereitstellen.