1. 简介
在此 Codelab 中,您将为咖啡店构建一个交互式 AI 咖啡师智能体。您将使用 Google 的开源智能体开发套件 (ADK) 和 Gemini 3.5 Flash 模型,实现检索增强生成 (RAG),以便根据模拟菜单数据集为智能体的推荐提供依据。最后,您将使用 Streamlit 界面封装智能体,并将其部署到 Cloud Run 。
您将执行的操作
- 创建一个 RAG 数据源 (
menu.json),其中包含咖啡商品、标签和过敏原。 - 使用 ADK
LlmAgent构建 AI 智能体,并连接 Python 工具以加载菜单数据。 - 使用 Streamlit 聊天应用封装智能体,以管理对话记录。
- 使用基于源代码的部署将 Streamlit 应用部署到 Cloud Run。
- 测试 RAG 依据和过敏原感知。
所需条件
- Web 浏览器,例如 Chrome。
- 启用了结算功能的 Google Cloud 项目。
- 基本熟悉 Python。
此 Codelab 适用于各种水平的开发者,包括新手。
预计费用: 不到 1 美元。
2. 准备工作
创建 Google Cloud 项目
- 在 Google Cloud 控制台中,选择或创建 Google Cloud 项目。
- 确保您的 Cloud 项目已启用结算功能。
启动 Cloud Shell
- 点击 Google Cloud 控制台顶部的激活 Cloud Shell 。
- 核实身份验证:
gcloud auth list
- 确认已设置活跃项目:
gcloud config get project
如果显示的项目 ID 不正确或未设置任何项目 ID,请运行以下命令:
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 咖啡师有依据,并防止其虚构不存在的商品,您将创建一个本地菜单数据集。智能体将在运行时通过自定义工具读取此文件。
- 在 Cloud Shell Editor 中创建并打开
menu.json:
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。
使用实时数据库可让咖啡店经理动态添加季节性商品、更新价格或调整过敏原标签,而无需重新构建容器映像或重新部署应用代码。
5. 构建 ADK 智能体
现在,您将安装所需的软件包并构建核心 ADK 智能体逻辑。您将定义 get_menu() 工具并将其传递给 LlmAgent。
- 在 Cloud Shell Editor 中创建并打开
requirements.txt:
cloudshell edit requirements.txt
- 将以下依赖项粘贴到编辑器中,然后保存该文件:
google-adk==2.2.0
streamlit==1.58.0
- 在 Cloud Shell Editor 中创建并打开
agent.py:
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-flash-latest",
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
)
- 在 Cloud Shell Editor 中创建并打开
app.py:
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}")
💬 讨论:模型权衡和检索令牌效率
为什么我们选择
gemini-3.5-flash
?为什么我们指定了确切的版本字符串,而不是使用通用的最新标记?
Gemini 3.5 Flash 专为速度和成本效益而设计,非常适合交互式聊天智能体。
对于确切的版本字符串,我们还指定了 gemini-3.5-flash,因为区域性 Vertex AI 端点(例如 us-central1)并不总是支持通用别名(例如
gemini-flash-latest)。在本实验中,我们将位置变量配置为 global,以便通过全局端点访问
gemini-3.5-flash。
为什么调用函数工具来检索菜单,而不是直接将整个菜单文本粘贴到智能体的系统说明中?
为了节省令牌!在提示中添加 8 个商品很便宜,但如果咖啡店扩展到 500 个商品(包括自定义配料)呢?将大型数据集直接粘贴到系统提示中会增加提示 token 数量,从而增加每次查询的交易费用和 API 响应延迟时间。
通过使用 ADK 工具,智能体仅在需要时才动态请求读取菜单。LLM 仅接收相关的菜单数据作为上下文,从而最大限度地减少提示令牌大小。
💬 讨论:内存状态和生产存储
当用户关闭浏览器标签页时,存储在 Streamlit 的
st.session_state
中的聊天记录是否会在用户关闭浏览器标签页时保留?
不会。st.session_state 完全在内存中,并且对于活跃的浏览器连接是唯一的。如果用户刷新页面或关闭标签页,他们与咖啡师的对话记录将会丢失。
对于生产环境应用,您会将 ADK 运行程序连接到永久性存储后端,例如 Cloud Firestore 或 Redis。ADK 提供内置服务抽象(例如
SessionService),可让您轻松地跨页面重新加载和设备保存和恢复聊天记录。
6. 将智能体部署到 Cloud Run
您将使用 Cloud Run 的内置 buildpack 直接从源代码部署 Streamlit 应用。为了遵循最小权限原则,您将使用专用的自定义服务账号(而不是使用默认的 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"
- 向新服务账号授予 Vertex AI 用户角色 (
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 \
--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
- 部署完成后,在命令输出中找到服务网址。
💬 讨论:部署容器与源代码,以及 IAM 安全性
我们使用 Cloud Run 部署到
gcloud run deploy --source
部署到 Cloud Run,而无需创建
Dockerfile
或
Procfile
。Cloud Run 如何确定如何编译和执行我们的 Python 应用?
Cloud Run 在后台使用 Buildpack 来分析您的代码库。检测到 requirements.txt 和 Python
源文件后,引擎会自动编译并打包优化的 Python 运行时容器。
编写自定义 Dockerfile 可让您完全控制容器的系统软件包和基础层。Procfile
是一种更简单的方式来声明启动命令,而无需完全配置容器。但对于快速部署,从源代码 (--source) 部署非常高效。
为什么我们要执行额外的步骤来创建自定义服务账号
barista-agent-sa
,而不是直接使用默认的 Compute Engine 服务账号?
安全第一!默认的 Compute Engine 服务账号默认具有非常广泛的 Editor 权限。在默认服务账号下运行 Cloud Run 容器意味着,如果我们的应用存在安全 bug,攻击者可能会读取、写入或删除 Google Cloud 项目中的其他资源。
通过创建专用服务账号并仅为其分配 roles/aiplatform.user 角色,我们遵循了最小权限原则:应用仅具有调用 Gemini
所需的访问权限,而没有其他权限。
7. 测试 RAG 行为
在 Web 浏览器中打开 Cloud Run 服务网址,并向 AI 咖啡师提问,以测试其依据和安全限制。
- 菜单内请求: 提问:“推荐一些浓郁而温暖的饮品。”预期: 智能体推荐浓缩咖啡。
- 菜单外陷阱: 提问:“你们有抹茶星冰乐吗?”预期:智能体礼貌地拒绝并解释说菜单上没有。
- 过敏原感知请求: 提问:“我对乳糖不耐受,有什么可以喝的?”预期: 智能体仅推荐不含乳制品的菜单项(例如燕麦奶拿铁、浓缩咖啡、冷萃咖啡)。它不会推荐卡布奇诺或牛角面包。
8. 可选:使用 Vector Search 在 Firestore 中为智能体提供依据
在生产场景中,将菜单项存储在本地 menu.json 文件中并不理想,因为对菜单的任何更改都需要重新构建容器映像并重新部署 Cloud Run
服务。
为了使应用具有动态性和可伸缩性,您可以将菜单数据迁移到 Cloud Firestore ,并使用Vector Search 仅检索语义相似度最高的菜单项。
1. 启用 Firestore API 并初始化数据库
运行以下命令以启用 Firestore API,并在原生模式下创建名为 coffee-menu 的 Firestore 数据库:
gcloud services enable firestore.googleapis.com
gcloud firestore databases create --database="coffee-menu" --location=$REGION
[!NOTE] 注意: API 启用可能需要 1-2 分钟才能传播。如果数据库创建命令提示您 API [firestore.googleapis.com] not enabled on project... Would you like to enable and retry?,请输入
Y 以继续,或等待一分钟,然后重新运行该命令。
2. 使用菜单数据为 Firestore 播种
如需使用 menu.json 文件中的菜单项快速为 Firestore 数据库播种,您可以在 Cloud Shell 中本地运行 Python
脚本。
- 在 Cloud Shell 中本地安装 Firestore 和 GenAI 客户端库,以运行播种脚本:
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 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!")
- 运行脚本:
python3 seed.py
3. 创建 Firestore 向量索引
如需对菜单项执行向量搜索,您必须在 Firestore 数据库中的 embedding 字段上创建复合向量索引。
在 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": "{}"}'
[!NOTE] 注意: 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,但 Google Cloud 使用统一的 Cloud Datastore IAM 角色(roles/datastore.viewer 或 roles/datastore.user)来管理访问权限控制。
5. 更新代码
现在,更新您的代码以从 Firestore 检索菜单,而不是从 menu.json 读取。
- 在 Cloud Shell Editor 中打开
requirements.txt:
cloudshell edit requirements.txt
- 将 Firestore 和 GenAI 客户端库附加到文件末尾并保存:
google-cloud-firestore==2.27.0
google-genai==2.11.0
- 在 Cloud Shell Editor 中打开
agent.py:
cloudshell edit agent.py
- 在
agent.py中找到# [START get_menu]代码块,并将其完全替换(从# [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-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]
- 在 Cloud Shell Editor 中打开
app.py:
cloudshell edit app.py
- 在
app.py中找到# [START load_menu]代码块,并将其完全替换(从# [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 \
--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. 验证 Firestore 集成
如需测试智能体与 Firestore 的连接,请直接在 Firestore 中添加一个全新的菜单项,并验证智能体是否会推荐该菜单项。
- 在 Cloud Shell 中运行以下命令,以使用 Python 将新文档写入 Firestore 中的
menu集合:
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!')
"
- 在浏览器中刷新 Streamlit 应用,以清除聊天会话并加载新的数据库状态。
- 请注意:
- 抹茶拿铁 会自动显示在边栏菜单中。
- 向聊天机器人提问:“你们有抹茶饮品吗?”
- 智能体应成功推荐您刚刚添加了说明和价格的新抹茶拿铁 。这证实了智能体直接在您的实时 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
可选步骤:删除整个项目。⚠️仅当您为此实验创建了专用项目时才执行此操作
gcloud projects delete $PROJECT_ID
10. 恭喜
恭喜!您已使用 Google 的 ADK 和 Cloud Run 构建并部署了检索增强生成 (RAG) AI 咖啡师智能体。
您学到的内容
- 在 Python 中构建简单的 RAG 工具。
- 利用 ADK
LlmAgent和InMemoryRunner。 - 在 Streamlit 中创建有状态的聊天体验。
- 使用基于源代码的构建将 Streamlit 部署到 Cloud Run。