1. 准备工作
什么是 Vertex AI?
Vertex AI 是 Google Cloud 的统一开发平台,用于构建、部署和扩缩企业级 AI 智能体和应用。该平台为开发者和数据科学家提供了先进的工具,可用于设计与全球规模的基础设施深度集成的自定义智能体工作流。
- 访问 Model Garden:从 150 多种基础模型(包括完整的 Gemini 系列、第三方模型和专门的开源模型)中进行选择,找到最适合特定代理任务的模型。
- 设计复杂的编排:Vertex AI 提供了一个框架,用于设计自主智能体,这些智能体使用推理来规划、执行多步任务和调用外部 API。
- 企业级依据:将智能体连接到实时业务数据,包括高性能 RAG(检索增强生成),以消除幻觉并确保事实准确性。
- DevOps:借助强大的 SDK、API 和评估工具,将代理开发无缝集成到现有的 CI/CD 流水线中,以便大规模衡量代理性能和安全性。
- 工业级安全性:Vertex AI 可确保用于训练或提供依据的客户数据保持私密状态、经过加密,并符合全球数据留存要求。
- 优化后的基础设施:在 Google 的世界级 TPU 和 GPU 集群中轻松扩缩 agentic 工作负载,即使对于要求最严苛的全球应用,也能确保低延迟性能。
什么是 Google Workspace?
Google Workspace 是一套基于云的高效办公和协作解决方案,专为个人、学校和企业打造:
- 沟通:专业电子邮件服务 (Gmail)、视频会议 (Meet) 和团队消息服务 (Chat)。
- 内容创作:用于撰写文档 (Google 文档)、构建电子表格 (Google 表格) 和设计演示文稿 (Google 幻灯片) 的工具。
- 组织:共享日历(日历)和数字记事(Keep)。
- 存储空间:用于安全地保存和共享文件的集中式云空间(云端硬盘)。
- 管理:用于管理用户和安全设置的管理控件(Workspace 管理控制台)。
哪些类型的自定义集成?
Google Workspace 和 Vertex AI 形成强大的反馈环路,其中 Workspace 提供实时数据和协作背景信息,而 Vertex AI 提供自动化智能工作流所需的模型、智能体推理和编排。
- 智能连接:Google 管理的数据存储区、API 和 MCP 服务器(Google 管理的服务器和自定义服务器)可让代理安全无缝地访问 Workspace 数据,并代表用户执行操作。
- 自定义代理:团队可以使用无代码设计器或专业代码框架,基于管理员管控的 Workspace 数据和操作构建专业代理。
- 原生集成:无论是通过专用界面组件还是后台进程,Workspace 加购项都能弥合 AI 系统与 Chat 和 Gmail 等应用之间的差距。这样,客服人员就可以在用户需要时立即提供贴合情境的帮助。
通过将 Google Workspace 强大的效率生态系统与 Vertex AI 先进的智能体功能相结合,组织可以利用自定义的、基于数据的 AI 智能体来转变运营方式,直接在团队日常使用的工具中自动执行复杂的工作流程。
前提条件
如果您想在自己的环境中完成所有步骤,则需要:
- 具备 Google Cloud 和 Python 基础知识。
- 您是所有者且已启用结算功能的 Google Cloud 项目。如需检查现有项目是否已启用结算功能,请参阅验证项目的结算状态。如需创建项目并设置结算,请参阅创建 Google Cloud 项目。如需更改项目所有权,请参阅管理项目成员或更改项目所有权。
- 拥有可访问 Google Chat 的 Google Workspace 商务版或企业版账号,并且已启用智能功能。
- Google Cloud CLI 已针对您的 Google Cloud 项目安装并初始化。
- 已安装 Python 3.11+,请参阅官方 Python 网站上的说明。
构建内容
在此 Codelab 中,我们将构建三个与 Google Workspace 紧密集成的 Vertex AI 代理解决方案。他们将展示可用于与数据、操作和界面互动的架构模式。
Vertex AI Search 应用
借助此代理,用户可以使用自然语言搜索数据并针对 Workspace 执行操作。它依赖于以下元素:
- 模型:Gemini。
- 数据和操作:适用于 Google Workspace(日历、Gmail、云端硬盘)的 Vertex AI 数据存储区。
- 代理主机:Vertex AI Search。
- 界面:Vertex AI Search Web Widget。

自定义代理
借助此代理,用户可以使用自定义工具和规则,以自然语言搜索数据并针对 Workspace 执行操作。它依赖于以下元素:
- 模型:Gemini。
- 数据和操作:Google Workspace(日历、Gmail、云端硬盘)的 Vertex AI 数据存储区、Google 管理的 Vertex AI Search 模型上下文协议 (MCP) 服务器、用于发送 Google Chat 消息(通过 Google Chat API)的自定义工具函数。
- 智能体构建工具:智能体开发套件 (ADK)。
- 代理主机:Vertex AI Agent Engine。
- 界面:ADK Web。


作为 Google Workspace 加购项的代理
借助此代理,用户可以在 Workspace 应用界面中以自然语言搜索 Workspace 数据。它依赖于以下元素:
- 模型:Gemini。
- 数据和操作:Google Workspace(日历、Gmail、云端硬盘)的 Vertex AI 数据存储区、Google 管理的 Vertex AI Search 模型上下文协议 (MCP) 服务器、用于发送 Google Chat 消息(通过 Google Chat API)的自定义工具函数。
- 智能体构建工具:智能体开发套件 (ADK)。
- 代理主机:Vertex AI Agent Engine。
- 界面:适用于 Chat 和 Gmail 的 Google Workspace 加购项(可轻松扩展到 Google 日历、云端硬盘、文档、表格和幻灯片)。
- Google Workspace 插件:Apps 脚本、Vertex AI Agent Engine API、上下文(所选 Gmail 邮件)。


学习内容
- Vertex AI Search 与 Google Workspace 之间可实现数据和操作的集成点。
- 用于构建托管在 Vertex AI 中的自定义代理的选项。
- 用户可以访问智能体的方式,例如 Vertex AI Search Web Widget 和 Google Workspace 应用。
2. 设置
在构建解决方案之前,我们必须初始化项目的 Vertex AI Applications 设置,启用所需的 API,并创建 Vertex AI Workspace 数据存储区。
查看概念
Vertex AI 应用
Vertex AI 应用是 Google Cloud 上的一款托管式端到端解决方案,可将机器学习模型(例如生成式 AI 代理或搜索引擎)与企业数据和专用工具集成,以执行语义搜索、内容生成或自动化客户互动等复杂任务。
Vertex AI 数据存储区
Vertex AI 数据存储区是一种实体,其中包含从第一方数据源(例如 Google Workspace)或第三方应用(例如 Jira 或 Shopify)提取的数据。包含第三方应用数据的数据存储区也称为数据连接器。
启动 Vertex AI 应用设置
初始化 Vertex AI Applications 设置以启用智能体创建。
在新标签页中打开 Google Cloud 控制台,然后按以下步骤操作:
- 选择您的项目。
- 在 Google Cloud 搜索字段中,前往 AI Applications
- 查看并同意相关条款后,点击继续并激活 API。
- 打开 Settings(设置)。
- 在身份验证标签页中,修改全局。

- 选择 Google Identity,然后点击保存。

启用 API
Vertex AI Workspace 数据存储区需要启用以下 API:
- 在 Google Cloud 控制台中,启用 Calendar API、Gmail API 和 People API:

- 依次点击菜单 ☰ > API 和服务 > 已启用的 API 和服务,然后确认列表中包含 Google Calendar API、Gmail API 和 People API。
创建数据存储区
创建 Google 云端硬盘数据存储区:
- 在 Google Cloud 控制台中,前往 AI Applications,然后前往数据存储区。
- 点击 + 创建数据存储区。
- 在来源中的 Google 云端硬盘下方,点击选择。

- 在数据中,选择全部,然后点击继续。

- 在配置中,将数据连接器名称设置为
drive,然后在查看并同意可能产生的费用后,点击继续。

- 在价格中,选择您偏好的定价模式,然后点击创建。在此 Codelab 中,建议使用一般价格。
- 系统会自动将您重定向到数据存储区,您可以在其中看到新添加的数据存储区。
创建 Google 日历数据存储区:
- 点击 + 创建数据存储区。
- 在来源中,搜索 Google 日历,然后点击选择。
- 在操作部分,点击跳过。
- 在配置部分中,将数据连接器名称设置为
calendar。 - 点击创建。
- 系统会自动将您重定向到数据存储区,您可以在其中看到新添加的数据存储区。
创建 Google Gmail 数据存储区:
- 点击 + 新建数据存储区。
- 在来源中,搜索 Google Gmail,然后点击选择。
- 在操作部分,点击跳过。
- 在配置部分中,将数据连接器名称设置为
gmail。 - 点击创建。
- 系统会自动将您重定向到数据存储区,您可以在其中看到新添加的数据存储区。
3. Vertex AI Search 应用
借助此代理,用户可以使用自然语言搜索数据并针对 Workspace 执行操作。它依赖于以下元素:
- 模型:Gemini。
- 数据和操作:适用于 Google Workspace(日历、Gmail、云端硬盘)的 Vertex AI 数据存储区。
- 代理主机:Vertex AI Search。
- 界面:Vertex AI Search Web Widget。
查看概念
Vertex AI Search 应用
Vertex AI Search 应用可为最终用户提供搜索结果、操作和智能体。在 API 的上下文中,“应用”一词可以与“引擎”一词互换使用。应用必须连接到数据存储区,才能使用其中的数据来提供搜索结果、答案或操作。
Vertex AI Search Web Widget
Vertex AI Search Web Widget 是一种预构建的可自定义的界面组件,可让开发者通过最少的编码将 AI 赋能的搜索栏和结果界面直接嵌入到网站中。
Vertex AI Search 预览版
Vertex AI Search 预览版是 Google Cloud 控制台中的内置测试环境,可让开发者在将搜索配置和生成式回答无缝部署到可用于生产环境的 Vertex AI Search 网络 widget 之前,先对其进行验证。
查看解决方案架构

创建应用
创建新的搜索应用以锚定数据存储区。
在 Cloud 控制台中打开 AI Applications > Apps,然后按以下步骤操作:
- 点击 + 创建应用。
- 在类型下,点击自定义搜索(常规)下的创建。

- 在配置中,查看并同意价格后,选中企业版功能和生成式回答。
- 将应用名称设置为
codelab。 - 系统会根据名称生成 ID,并显示在相应字段下方,请复制该 ID。
- 将公司名称设置为
Codelab。 - 将多区域设置为
global (Global)。 - 点击继续。

- 在数据中,选择数据存储区 drive、gmail 和 calendar,然后点击继续。

- 在价格中,选择您偏好的定价模式,然后点击创建。在此 Codelab 中,建议使用一般价格。
- 应用创建完毕后,系统会自动将您重定向到 AI Applications > 应用 > codelab > 应用概览。
- 前往关联的数据存储区。
- 几分钟后,所有已连接的数据存储区状态都应为有效。

配置 Web widget
配置搜索微件的视觉外观和行为。
- 前往配置。
- 在界面标签页中,将搜索类型设置为支持后续问题的搜索,然后点击保存并发布。

试用应用
直接在 Google Cloud 控制台中测试搜索应用。
- 前往预览,系统会显示 Web widget。
- 在聊天中,输入
Do I have any meetings today?,然后按enter。 - 在聊天中,输入
Did I receive an email on March 1st 2026?,然后按enter。 - 在聊天中,输入
Give me the title of the latest Drive file I created,然后按enter。

4. 自定义代理
借助此代理,用户可以使用自定义工具和规则,以自然语言搜索数据并针对 Workspace 执行操作。它依赖于以下元素:
- 模型:Gemini。
- 数据和操作:Google Workspace(日历、Gmail、云端硬盘)的 Vertex AI 数据存储区、Google 管理的 Vertex AI Search 模型上下文协议 (MCP) 服务器、用于发送 Google Chat 消息(通过 Google Chat API)的自定义工具函数。
- 智能体构建工具:智能体开发套件 (ADK)。
- 代理主机:Vertex AI Agent Engine。
- 界面:ADK Web。
查看概念
智能体开发套件 (ADK)
智能体开发套件 (ADK) 是一套专门的工具和框架,旨在通过提供用于推理、内存管理和工具集成的预构建模块,简化自主 AI 智能体的创建过程。
Model Context Protocol (MCP)
Model Context Protocol (MCP) 是一种开放标准,旨在通过通用的“即插即用”接口,在 AI 应用与各种数据源或工具之间实现无缝、安全的集成。
函数工具
函数工具是一种预定义的执行例程,AI 模型可以触发该例程来执行特定操作或从外部系统检索实时数据,从而将其功能扩展到简单的文本生成之外。
ADK Web
ADK Web 是 ADK SDK 随附的内置开发界面,可简化开发和调试流程。
查看解决方案架构

查看源代码
agent.py
以下代码可用于向 Vertex AI 进行身份验证、初始化 Vertex AI Search MCP 和 Chat API 工具,以及定义代理的行为。
- 身份验证:它从环境变量中检索
ACCESS_TOKEN,以对 MCP 和 API 调用进行身份验证。 - 工具设置:它会初始化
vertexai_mcp(一种连接到 Vertex AI Search Model Context Protocol (MCP) 服务器的工具集)和send_direct_message工具。这样,代理便能够搜索您关联的数据存储区并发送 Google Chat 消息。 - 智能体定义:它使用
gemini-2.5-flash模型定义root_agent。这些指令会告知代理优先使用搜索工具来检索信息,并使用send_direct_message工具来执行操作,从而有效地让代理基于企业数据运行。
...
MODEL = "gemini-2.5-flash"
# Access token for authentication
ACCESS_TOKEN = os.environ.get("ACCESS_TOKEN")
if not ACCESS_TOKEN:
raise ValueError("ACCESS_TOKEN environment variable must be set")
VERTEXAI_SEARCH_TIMEOUT = 15.0
def get_project_id():
"""Fetches the consumer project ID from the environment natively."""
_, project = google.auth.default()
if project:
return project
raise Exception(f"Failed to resolve GCP Project ID from environment.")
def find_serving_config_path():
"""Dynamically finds the default serving config in the engine."""
project_id = get_project_id()
engines = discoveryengine_v1.EngineServiceClient().list_engines(
parent=f"projects/{project_id}/locations/global/collections/default_collection"
)
for engine in engines:
# engine.name natively contains the numeric Project Number
return f"{engine.name}/servingConfigs/default_serving_config"
raise Exception(f"No Discovery Engines found in project {project_id}")
def send_direct_message(email: str, message: str) -> dict:
"""Sends a Google Chat Direct Message (DM) to a specific user by email address."""
chat_client = chat_v1.ChatServiceClient(
credentials=Credentials(token=ACCESS_TOKEN)
)
# 1. Setup the DM space or find existing one
person = chat_v1.User(
name=f"users/{email}",
type_=chat_v1.User.Type.HUMAN
)
membership = chat_v1.Membership(member=person)
space_req = chat_v1.Space(space_type=chat_v1.Space.SpaceType.DIRECT_MESSAGE)
setup_request = chat_v1.SetUpSpaceRequest(
space=space_req,
memberships=[membership]
)
space_response = chat_client.set_up_space(request=setup_request)
space_name = space_response.name
# 2. Send the message
msg = chat_v1.Message(text=message)
message_request = chat_v1.CreateMessageRequest(
parent=space_name,
message=msg
)
message_response = chat_client.create_message(request=message_request)
return {"status": "success", "message_id": message_response.name, "space": space_name}
vertexai_mcp = McpToolset(
connection_params=StreamableHTTPConnectionParams(
url="https://discoveryengine.googleapis.com/mcp",
timeout=VERTEXAI_SEARCH_TIMEOUT,
sse_read_timeout=VERTEXAI_SEARCH_TIMEOUT,
headers={"Authorization": f"Bearer {ACCESS_TOKEN}"}
),
tool_filter=['search']
)
# Answer nicely the following user queries:
# - Please find my meetings for today, I need their titles and links
# - What is the latest Drive file I created?
# - What is the latest Gmail message I received?
# - Please send the following message to someone@example.com: Hello, this is a test message.
root_agent = LlmAgent(
model=MODEL,
name='enterprise_ai',
instruction=f"""
You are a helpful assistant that always uses the Vertex AI MCP search tool to answer the user's message, unless the user asks you to send a message to someone.
If the user asks you to send a message to someone, use the send_direct_message tool to send the message.
You MUST unconditionally use the Vertex AI MCP search tool to find answer, even if you believe you already know the answer or believe the Vertex AI MCP search tool does not contain the data.
The Vertex AI MCP search tool accesses the user's data through datastores including Google Drive, Google Calendar, and Gmail.
Only use the Vertex AI MCP search tool with servingConfig and query parameters, do not use any other parameters.
Always use the servingConfig {find_serving_config_path()} while using the Vertex AI MCP search tool.
""",
tools=[vertexai_mcp, FunctionTool(send_direct_message)]
)
下载源代码
将示例代码下载到本地环境,即可开始使用。
- 下载此 GitHub 代码库。
- 在终端中,打开
solutions/enterprise-ai-agent-local目录。
启用 API
此解决方案需要启用其他 API:
- 在 Google Cloud 控制台中,启用 Vertex AI、Cloud Resource Manager 和 Google Chat API:

- 依次点击菜单 ☰ > API 和服务 > 已启用的 API 和服务,然后确认列表中包含 Vertex AI API、Cloud Resource Manager API 和 Google Chat API。
配置 OAuth 权限请求屏幕
此解决方案需要配置权限请求页面:
- 在 Google Cloud 控制台中,依次点击菜单 ☰ > Google Auth Platform > 品牌推广。
- 点击开始使用。
- 在应用信息下,将应用名称设置为
Codelab。 - 在用户支持电子邮件中,选择一个支持电子邮件地址,以便用户在对自己的同意情况有疑问时与您联系。
- 点击下一步。
- 在受众下,选择内部。
- 点击下一步。
- 在联系信息下,输入一个电子邮件地址,以便您接收有关项目变更的通知。
- 点击下一步。
- 在完成部分,查看 Google API 服务用户数据政策,如果您同意该政策,请选择我同意 Google API 服务:用户数据政策。
- 依次点击继续和创建。

- 系统会保存配置,并自动将您重定向到 Google Auth Platform > 概览。
如需了解详情,请参阅完整的配置 OAuth 权限请求指南。
创建 OAuth 客户端凭据
创建新的桌面应用 OAuth 客户端,以便在本地环境中对用户进行身份验证:
- 在 Google Cloud 控制台中,依次点击菜单 ☰ > Google Auth 平台 > 客户端。
- 点击 + 创建客户端。
- 对于应用类型,选择桌面应用。
- 将名称设置为
codelab。 - 点击创建。系统会显示新创建的凭据。
- 点击下载 JSON,然后将文件另存为
solutions/enterprise-ai-agent-local目录中的 client_secret.json。

启用 Vertex AI Search MCP
- 在终端中,执行以下命令:
gcloud beta services mcp enable discoveryengine.googleapis.com \
--project=$(gcloud config get-value project)
配置 Chat 应用
配置 Google Chat 应用的基本信息详情。
- 在 Google Cloud 控制台中,在 Google Cloud 搜索字段中搜索
Google Chat API,点击 Google Chat API,然后依次点击管理和配置。
- 将应用名称和说明设置为
Vertex AI。 - 将头像网址设置为
https://developers.google.com/workspace/add-ons/images/quickstart-app-avatar.png。 - 取消选中启用互动功能,然后在随即显示的模态对话框中点击停用。
- 选择将错误记录到 Logging。
- 点击保存。

在 ADK Web 中运行代理
使用 ADK 网页界面在本地启动智能体。
- 在终端中,打开
solutions/enterprise-ai-agent-local目录,然后执行以下命令:
# 1. Authenticate with all the required scopes gcloud auth application-default login \ --client-id-file=client_secret.json \ --scopes=https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/chat.spaces,https://www.googleapis.com/auth/chat.messages # 2. Configure environment export ACCESS_TOKEN=$(gcloud auth application-default print-access-token) export GOOGLE_GENAI_USE_VERTEXAI=1 export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project) export GOOGLE_CLOUD_LOCATION=us-central1 # 3. Create and activate a new virtual environment python3 -m venv .venv source .venv/bin/activate # 4. Install poetry and project dependencies pip install poetry poetry install # 5. Start ADK Web adk web

试用代理
通过与自定义代理聊天来验证流程。
- 在互联网浏览器中,打开 ADK 网站。
- 在聊天中,输入
Please find my meetings for today, I need their titles and links,然后按enter。 - 智能体回答时会列出日历活动(具体取决于用户的账号)。
- 在聊天中,输入
Please send a Chat message to someone@example.com with the following text: Hello!,然后按enter。 - 代理会以确认消息做出回答。


5. 以 Google Workspace 加购项形式提供的代理
借助此代理,用户可以在 Workspace 应用界面中以自然语言搜索 Workspace 数据。它依赖于以下元素:
- 模型:Gemini。
- 数据和操作:Google Workspace(日历、Gmail、云端硬盘)的 Vertex AI 数据存储区、Google 管理的 Vertex AI Search 模型上下文协议 (MCP) 服务器、用于发送 Google Chat 消息(通过 Google Chat API)的自定义工具函数。
- 智能体构建工具:智能体开发套件 (ADK)。
- 代理主机:Vertex AI Agent Engine。
- 界面:适用于 Chat 和 Gmail 的 Google Workspace 加购项(可轻松扩展到 Google 日历、云端硬盘、文档、表格和幻灯片)。
- Google Workspace 插件:Apps 脚本、Vertex AI Agent Engine API、上下文(所选 Gmail 邮件)。
查看概念
Google Workspace 加购项
Google Workspace 加购项是一种自定义应用,可扩展一个或多个 Google Workspace 应用(Gmail、Chat、Google 日历、Google 文档、云端硬盘、Meet、Google 表格和 Google 幻灯片)。
Apps 脚本
Apps 脚本是一个基于云的 JavaScript 平台,由 Google 云端硬盘提供支持,可让您与 Google 各项产品集成并自动执行任务。
Google Workspace 卡片框架
Google Workspace 中的 Card 框架可让开发者创建丰富多样的互动式界面。借助它,您可以构建包含文本、图片、按钮和其他 widget 的有条理且美观的卡片。这些卡片可提供结构化信息,并直接在 Workspace 应用中启用快捷操作,从而提升用户体验。
查看解决方案架构

查看源代码
Agent
agent.py
以下代码可用于向 Vertex AI 进行身份验证、初始化 Vertex AI Search MCP 和 Chat API 工具,以及定义代理的行为。
- 身份验证:它使用辅助函数
_get_access_token_from_context来检索客户端注入的身份验证令牌 (CLIENT_AUTH_NAME)。此令牌对于安全调用下游服务(例如 Vertex AI Search MCP 和 Google Chat 工具)至关重要。 - 工具设置:它会初始化
vertexai_mcp(一种连接到 Vertex AI Search Model Context Protocol (MCP) 服务器的工具集)和send_direct_message工具。这样,代理便能够搜索您关联的数据存储区并发送 Google Chat 消息。 - 智能体定义:它使用
gemini-2.5-flash模型定义root_agent。这些指令会告知代理优先使用搜索工具来检索信息,并使用send_direct_message工具来执行操作,从而有效地让代理基于企业数据运行。
...
MODEL = "gemini-2.5-flash"
# Client injects a bearer token into the ToolContext state.
# The key pattern is "CLIENT_AUTH_NAME_<random_digits>".
# We dynamically parse this token to authenticate our MCP and API calls.
CLIENT_AUTH_NAME = "enterprise-ai"
VERTEXAI_SEARCH_TIMEOUT = 15.0
def get_project_id():
"""Fetches the consumer project ID from the environment natively."""
_, project = google.auth.default()
if project:
return project
raise Exception(f"Failed to resolve GCP Project ID from environment.")
def find_serving_config_path():
"""Dynamically finds the default serving config in the engine."""
project_id = get_project_id()
engines = discoveryengine_v1.EngineServiceClient().list_engines(
parent=f"projects/{project_id}/locations/global/collections/default_collection"
)
for engine in engines:
# engine.name natively contains the numeric Project Number
return f"{engine.name}/servingConfigs/default_serving_config"
raise Exception(f"No Discovery Engines found in project {project_id}")
def _get_access_token_from_context(tool_context: ToolContext) -> str:
"""Helper method to dynamically parse the intercepted bearer token from the context state."""
escaped_name = re.escape(CLIENT_AUTH_NAME)
pattern = re.compile(fr"^{escaped_name}_\d+$")
# Handle ADK varying state object types (Raw Dict vs ADK State)
state_dict = tool_context.state.to_dict() if hasattr(tool_context.state, 'to_dict') else tool_context.state
matching_keys = [k for k in state_dict.keys() if pattern.match(k)]
if matching_keys:
return state_dict.get(matching_keys[0])
raise Exception(f"No bearer token found in ToolContext state matching pattern {pattern.pattern}")
def auth_header_provider(tool_context: ToolContext) -> dict[str, str]:
token = _get_access_token_from_context(tool_context)
return {"Authorization": f"Bearer {token}"}
def send_direct_message(email: str, message: str, tool_context: ToolContext) -> dict:
"""Sends a Google Chat Direct Message (DM) to a specific user by email address."""
chat_client = chat_v1.ChatServiceClient(
credentials=Credentials(token=_get_access_token_from_context(tool_context))
)
# 1. Setup the DM space or find existing one
person = chat_v1.User(
name=f"users/{email}",
type_=chat_v1.User.Type.HUMAN
)
membership = chat_v1.Membership(member=person)
space_req = chat_v1.Space(space_type=chat_v1.Space.SpaceType.DIRECT_MESSAGE)
setup_request = chat_v1.SetUpSpaceRequest(
space=space_req,
memberships=[membership]
)
space_response = chat_client.set_up_space(request=setup_request)
space_name = space_response.name
# 2. Send the message
msg = chat_v1.Message(text=message)
message_request = chat_v1.CreateMessageRequest(
parent=space_name,
message=msg
)
message_response = chat_client.create_message(request=message_request)
return {"status": "success", "message_id": message_response.name, "space": space_name}
vertexai_mcp = McpToolset(
connection_params=StreamableHTTPConnectionParams(
url="https://discoveryengine.googleapis.com/mcp",
timeout=VERTEXAI_SEARCH_TIMEOUT,
sse_read_timeout=VERTEXAI_SEARCH_TIMEOUT
),
tool_filter=['search'],
# The auth_header_provider dynamically injects the bearer token from the ToolContext
# into the MCP call for authentication.
header_provider=auth_header_provider
)
# Answer nicely the following user queries:
# - Please find my meetings for today, I need their titles and links
# - What is the latest Drive file I created?
# - What is the latest Gmail message I received?
# - Please send the following message to someone@example.com: Hello, this is a test message.
root_agent = LlmAgent(
model=MODEL,
name='enterprise_ai',
instruction=f"""
You are a helpful assistant that always uses the Vertex AI MCP search tool to answer the user's message, unless the user asks you to send a message to someone.
If the user asks you to send a message to someone, use the send_direct_message tool to send the message.
You MUST unconditionally use the Vertex AI MCP search tool to find answer, even if you believe you already know the answer or believe the Vertex AI MCP search tool does not contain the data.
The Vertex AI MCP search tool accesses the user's data through datastores including Google Drive, Google Calendar, and Gmail.
Only use the Vertex AI MCP search tool with servingConfig and query parameters, do not use any other parameters.
Always use the servingConfig {find_serving_config_path()} while using the Vertex AI MCP search tool.
""",
tools=[vertexai_mcp, FunctionTool(send_direct_message)]
)
客户
appsscript.json
以下配置定义了插件的触发器和权限。
- 定义插件:告知 Workspace 此项目是 Chat 和 Gmail 的插件。
- 情境触发器:对于 Gmail,它会设置一个
contextualTrigger,每当用户打开电子邮件时,该contextualTrigger就会触发onAddonEvent。这样,该插件就可以“看到”电子邮件内容。 - 权限:其中列出了插件运行所需的
oauthScopes,例如读取当前电子邮件、执行脚本和连接到外部服务(如 Vertex AI API)的权限。
...
"addOns": {
"common": {
"name": "Vertex AI",
"logoUrl": "https://developers.google.com/workspace/add-ons/images/quickstart-app-avatar.png"
},
"chat": {},
"gmail": {
"contextualTriggers": [
{
"unconditional": {},
"onTriggerFunction": "onAddonEvent"
}
]
}
},
"oauthScopes": [
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/gmail.addons.execute",
"https://www.googleapis.com/auth/gmail.addons.current.message.readonly"
]
...
Chat.gs
以下代码用于处理传入的 Google Chat 消息。
- 接收消息:
onMessage函数是消息互动的入口点。 - 管理上下文:它会将
space.name(即 Chat 空间的 ID)保存到用户的属性中。这样可确保当代理准备好回复时,它能准确知道要将消息发布到哪个对话中。 - 委托给代理:它会调用
requestAgent,并将用户的消息传递给处理 API 通信的核心逻辑。
...
// Service that handles Google Chat operations.
// Handle incoming Google Chat message events, actions will be taken via Google Chat API calls
function onMessage(event) {
if (isInDebugMode()) {
console.log(`MESSAGE event received (Chat): ${JSON.stringify(event)}`);
}
// Extract data from the event.
const chatEvent = event.chat;
setChatConfig(chatEvent.messagePayload.space.name);
// Request AI agent to answer the message
requestAgent(chatEvent.messagePayload.message);
// Respond with an empty response to the Google Chat platform to acknowledge execution
return null;
}
// --- Utility functions ---
// The Chat direct message (DM) space associated with the user
const SPACE_NAME_PROPERTY = "DM_SPACE_NAME"
// Sets the Chat DM space name for subsequent operations.
function setChatConfig(spaceName) {
const userProperties = PropertiesService.getUserProperties();
userProperties.setProperty(SPACE_NAME_PROPERTY, spaceName);
console.log(`Space is set to ${spaceName}`);
}
// Retrieved the Chat DM space name to sent messages to.
function getConfiguredChat() {
const userProperties = PropertiesService.getUserProperties();
return userProperties.getProperty(SPACE_NAME_PROPERTY);
}
// Finds the Chat DM space name between the Chat app and the given user.
function findChatAppDm(userName) {
return Chat.Spaces.findDirectMessage(
{ 'name': userName },
{'Authorization': `Bearer ${getAddonCredentials().getAccessToken()}`}
).name;
}
// Creates a Chat message in the configured space.
function createMessage(message) {
const spaceName = getConfiguredChat();
console.log(`Creating message in space ${spaceName}...`);
return Chat.Spaces.Messages.create(
message,
spaceName,
{},
{'Authorization': `Bearer ${getAddonCredentials().getAccessToken()}`}
).name;
}
Sidebar.gs
以下代码用于构建 Gmail 边栏并捕获电子邮件上下文。
- 构建界面:
createSidebarCard使用 Workspace 卡片服务构建直观的界面。它会创建一个包含文本输入区域和“发送消息”按钮的简单布局。 - 捕获电子邮件上下文:在
handleSendMessage中,代码会检查用户当前是否正在查看电子邮件 (event.gmail.messageId)。如果用户正在查看电子邮件,代码会安全地提取电子邮件的主题和正文,并将其附加到用户的提示中。 - 显示结果:代理做出回答后,代码会更新边栏卡片以显示回答。
...
// Service that handles Gmail operations.
// Triggered when the user opens the Gmail Add-on or selects an email.
function onAddonEvent(event) {
// If this was triggered by a button click, handle it
if (event.parameters && event.parameters.action === 'send') {
return handleSendMessage(event);
}
// Otherwise, just render the default initial sidebar
return createSidebarCard();
}
// Creates the standard Gmail sidebar card consisting of a text input and send button.
// Optionally includes an answer section if a response was generated.
function createSidebarCard(optionalAnswerSection) {
const card = CardService.newCardBuilder();
const actionSection = CardService.newCardSection();
// Create text input for the user's message
const messageInput = CardService.newTextInput()
.setFieldName("message")
.setTitle("Message")
.setMultiline(true);
// Create action for sending the message
const sendAction = CardService.newAction()
.setFunctionName('onAddonEvent')
.setParameters({ 'action': 'send' });
const sendButton = CardService.newTextButton()
.setText("Send message")
.setTextButtonStyle(CardService.TextButtonStyle.FILLED)
.setOnClickAction(sendAction);
actionSection.addWidget(messageInput);
actionSection.addWidget(CardService.newButtonSet().addButton(sendButton));
card.addSection(actionSection);
// Attach the response at the bottom if we have one
if (optionalAnswerSection) {
card.addSection(optionalAnswerSection);
}
return card.build();
}
// Handles clicks from the Send message button.
function handleSendMessage(event) {
const commonEventObject = event.commonEventObject || {};
const formInputs = commonEventObject.formInputs || {};
const messageInput = formInputs.message;
let userMessage = "";
if (messageInput && messageInput.stringInputs && messageInput.stringInputs.value.length > 0) {
userMessage = messageInput.stringInputs.value[0];
}
if (!userMessage || userMessage.trim().length === 0) {
return CardService.newActionResponseBuilder()
.setNotification(CardService.newNotification().setText("Please enter a message."))
.build();
}
let finalQueryText = `USER MESSAGE TO ANSWER: ${userMessage}`;
// If we have an email selected in Gmail, append its content as context
if (event.gmail && event.gmail.messageId) {
try {
GmailApp.setCurrentMessageAccessToken(event.gmail.accessToken);
const message = GmailApp.getMessageById(event.gmail.messageId);
const subject = message.getSubject();
const bodyText = message.getPlainBody() || message.getBody();
finalQueryText += `\n\nEMAIL THE USER HAS OPENED ON SCREEN:\nSubject: ${subject}\nBody:\n---\n${bodyText}\n---`;
} catch (e) {
console.error("Could not fetch Gmail context: " + e);
// Invalidate the token explicitly so the next prompt requests the missing scopes
ScriptApp.invalidateAuth();
CardService.newAuthorizationException()
.setResourceDisplayName("Enterprise AI")
.setAuthorizationUrl(ScriptApp.getAuthorizationUrl())
.throwException();
}
}
try {
const response = queryAgent({ text: finalQueryText });
// We leverage the 'showdown' library to parse the LLM's Markdown output into HTML
// We also substitute markdown listings with arrows and adjust newlines for clearer rendering in the sidebar
let displayedText = substituteListingsFromMarkdown(response.text);
displayedText = new showdown.Converter().makeHtml(displayedText).replace(/\n/g, '\n\n');
const textParagraph = CardService.newTextParagraph();
textParagraph.setText(displayedText);
const answerSection = CardService.newCardSection()
.addWidget(textParagraph);
const updatedCard = createSidebarCard(answerSection);
return CardService.newActionResponseBuilder()
.setNavigation(CardService.newNavigation().updateCard(updatedCard))
.build();
} catch (err) {
return CardService.newActionResponseBuilder()
.setNotification(CardService.newNotification().setText("Error fetching response: " + err.message))
.build();
}
}
...
AgentHandler.gs
以下代码可编排对 Vertex AI 的 API 调用。
- 编排 API 调用:
queryAgent是插件与 Vertex AI Agent Engine 之间的桥梁。它会构建一个请求,其中包含用户的查询和状态中的身份验证令牌。 - 流式传输回答:由于代理回答可能需要一段时间,因此它使用
streamQueryAPI 和服务器发送的事件 (SSE)。该代码以块的形式收集回答,并重构完整回答。
...
// Service that handles Vertex AI Agent operations.
// Submits a query to the AI agent and returns the response string synchronously
function queryAgent(input) {
let systemPrompt = "SYSTEM PROMPT START Do not respond with tables but use bullet points instead." +
" Do not ask the user follow-up questions or converse with them as history is not kept in this interface." +
" SYSTEM PROMPT END\n\n";
const requestPayload = {
"class_method": "async_stream_query",
"input": {
"user_id": "vertex_ai_add_on",
"message": { "role": "user", "parts": [{ "text": systemPrompt + input.text }] },
"state_delta": {
"enterprise-ai_999": `${ScriptApp.getOAuthToken()}`
}
}
};
const responseContentText = UrlFetchApp.fetch(
`https://${getLocation()}-aiplatform.googleapis.com/v1/${getReasoningEngine()}:streamQuery?alt=sse`,
{
method: 'post',
headers: { 'Authorization': `Bearer ${ScriptApp.getOAuthToken()}` },
contentType: 'application/json',
payload: JSON.stringify(requestPayload),
muteHttpExceptions: true
}
).getContentText();
if (isInDebugMode()) {
console.log(`Response: ${responseContentText}`);
}
const events = responseContentText.split('\n').map(s => s.replace(/^data:\s*/, '')).filter(s => s.trim().length > 0);
console.log(`Received ${events.length} agent events.`);
let author = "default";
let answerText = "";
for (const eventJson of events) {
if (isInDebugMode()) {
console.log("Event: " + eventJson);
}
const event = JSON.parse(eventJson);
// Retrieve the agent responsible for generating the content
author = event.author;
// Ignore events that are not useful for the end-user
if (!event.content) {
console.log(`${author}: internal event`);
continue;
}
// Handle text answers
const parts = event.content.parts || [];
const textPart = parts.find(p => p.text);
if (textPart) {
answerText += textPart.text;
}
}
return { author: author, text: answerText };
}
...
在 Vertex AI Agent Engine 中部署代理
- 在终端中,打开上一步下载的源代码中的
solutions/enterprise-ai-agent目录,然后执行以下命令:
# 1. Create and activate a new virtual environment deactivate python3 -m venv .venv source .venv/bin/activate # 2. Install poetry and project dependencies pip install poetry poetry install # 3. Deploy the agent adk deploy agent_engine \ --project=$(gcloud config get-value project) \ --region=us-central1 \ --display_name="Enterprise AI" \ enterprise_ai

- 当您在日志中看到“正在部署到代理引擎...”这一行时,请打开新终端并执行以下命令,以向 Vertex AI Reasoning Engine 服务代理添加所需权限:
# 1. Get the current Project ID
PROJECT_ID=$(gcloud config get-value project)
# 2. Extract the Project Number for that ID
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
# 3. Construct the Service Account name
SERVICE_ACCOUNT="service-${PROJECT_NUMBER}@gcp-sa-aiplatform-re.iam.gserviceaccount.com"
# 4. Apply the IAM policy binding
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT" \
--role="roles/discoveryengine.viewer"
- 等待 adk deploy 命令完成,然后从命令输出中以绿色显示的已部署的新智能体的资源名称。

启动服务账号
创建一个专用服务账号,以授权插件的服务器端操作。
在 Google Cloud 控制台中,按以下步骤操作:
- 依次点击菜单 ☰ > IAM 和管理 > 服务账号 > + 创建服务账号。
- 将服务账号名称设置为
vertexai-add-on。

- 点击完成。您将会转到服务账号页面,并看到创建的服务账号。

- 选择新创建的服务账号,然后选择密钥标签页。
- 依次点击添加密钥和创建新密钥。
- 选择 JSON,然后点击创建。

- 对话框随即关闭,新创建的公钥/私钥对将以 JSON 文件的形式自动下载到本地环境。
创建和配置 Apps 脚本项目
创建新的 Apps 脚本项目以托管插件代码,并配置其连接属性。
- 点击以下按钮,打开 Enterprise AI 加购项 Apps 脚本项目:
- 依次点击概览 > 复制。
- 在您的 Apps 脚本项目中,依次点击项目设置 > 修改脚本属性 > 添加脚本属性,以添加脚本属性。
- 将 REASONING_ENGINE_RESOURCE_NAME 设置为在之前的步骤中复制的 Vertex AI 代理资源名称。其格式如下:
projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ID>
- 将 APP_SERVICE_ACCOUNT_KEY 设置为在之前的步骤中下载的服务账号文件中的 JSON 密钥。
- 点击保存脚本属性
部署到 Gmail 和 Chat
部署该插件,以便直接在 Gmail 和 Google Chat 中对其进行测试。
在您的 Apps 脚本项目中,按以下步骤操作:
- 依次点击部署 > 测试部署,然后点击安装。现在,您可以在 Gmail 中使用此功能。
- 点击主要部署 ID 下方的复制。

在 Google Cloud 控制台中,按以下步骤操作:
- 在 Google Cloud 搜索字段中搜索
Google Chat API,点击 Google Chat API,然后依次点击管理和配置。
- 选择启用互动功能。
- 取消选择加入聊天室和群组对话。
- 在连接设置下,选择 Apps 脚本。
- 将部署 ID 设置为在上一步中复制的头部部署 ID。
- 在公开范围下,选择面向您 Workspace 网域中的特定人员和群组提供此 Chat 扩展应用,然后输入您的电子邮件地址。
- 点击保存。

试用加购项
与实时插件互动,验证它是否可以获取数据并回答情境化问题。
在新标签页中打开 Google Chat,然后按以下步骤操作:
- 打开与聊天应用 Vertex AI 的私信对话。

- 点击配置,然后完成身份验证流程。
- 输入
What are my meetings for today?,然后按enter。Vertex AI 聊天应用应回复结果。

在新标签页中打开 Gmail,然后按以下步骤操作:
- 给自己发送一封电子邮件,其中主题设置为
We need to talk,正文设置为Are you available today between 8 and 9 AM? - 打开新收到的电子邮件。
- 打开 Vertex AI 插件边栏。
- 将消息设置为
Do I have any meeting conflicts? - 点击发送讯息。
- 答案会显示在按钮下方。

6. 清理
删除 Google Cloud 项目
为避免系统因此 Codelab 中使用的资源向您的 Google Cloud 账号收取费用,我们建议您删除该 Google Cloud 项目。
在 Google Cloud 控制台中,按以下步骤操作:
- 依次点击菜单 ☰ > IAM 和管理 > 设置。
- 点击关停。
- 输入项目 ID。
- 点击仍要关停。

7. 恭喜
恭喜!您构建的解决方案可让 Vertex AI 和 Google Workspace 紧密集成,从而为工作人员提供助力!
后续操作
在此 Codelab 中,我们仅展示了最典型的用例,但您或许需要在解决方案中考虑很多值得扩展的地方,例如:
- 使用 Gemini CLI 和 Antigravity 等 AI 赋能的开发者工具。
- 与其他代理框架和工具集成,例如自定义 MCP、自定义函数调用和生成式界面。
- 与在 Vertex AI 等专用平台上托管的其他 AI 模型(包括自定义模型)集成。
- 与托管在专用平台(例如 Dialogflow)中或由第三方通过 Cloud Marketplace 托管的其他代理集成。
- 在 Cloud Marketplace 上发布代理,赋能团队、组织或公共用户。
了解详情
我们为开发者提供了大量资源,例如 YouTube 视频、文档网站、代码示例和教程:
- Google Cloud 开发者中心
- 支持的产品 | Google Cloud MCP 服务器
- A2UI
- Vertex AI 上的 Model Garden | Google Cloud
- Vertex AI Agent Engine 概览
- 获取回答和后续问题 | Vertex AI Search | Google Cloud 文档
- 通过 Google Cloud Marketplace 提供 AI 代理
- Google Workspace 开发者 YouTube 频道 - 欢迎开发者!
- Google Workspace 开发者网站
- 适用于所有 Google Workspace 加载项示例的 GitHub 代码库

