将 Vertex AI 智能体与 Google Workspace 集成

1. 准备工作

99afae2505f696fb.png

什么是 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 工作负载,即使对于要求最严苛的全球应用,也能确保低延迟性能。

127f2ed7d484722c.png

什么是 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 智能体来转变运营方式,直接在团队日常使用的工具中自动执行复杂的工作流程。

前提条件

如果您想在自己的环境中完成所有步骤,则需要:

构建内容

在此 Codelab 中,我们将构建三个与 Google Workspace 紧密集成的 Vertex AI 代理解决方案。他们将展示可用于与数据、操作和界面互动的架构模式。

Vertex AI Search 应用

借助此代理,用户可以使用自然语言搜索数据并针对 Workspace 执行操作。它依赖于以下元素:

  • 模型:Gemini。
  • 数据和操作:适用于 Google Workspace(日历、Gmail、云端硬盘)的 Vertex AI 数据存储区。
  • 代理主机:Vertex AI Search。
  • 界面:Vertex AI Search Web Widget。

d276ff8e2b9d0ddf.png

自定义代理

借助此代理,用户可以使用自定义工具和规则,以自然语言搜索数据并针对 Workspace 执行操作。它依赖于以下元素:

  • 模型:Gemini。
  • 数据和操作:Google Workspace(日历、Gmail、云端硬盘)的 Vertex AI 数据存储区、Google 管理的 Vertex AI Search 模型上下文协议 (MCP) 服务器、用于发送 Google Chat 消息(通过 Google Chat API)的自定义工具函数。
  • 智能体构建工具:智能体开发套件 (ADK)。
  • 代理主机:Vertex AI Agent Engine。
  • 界面:ADK Web。

145f47f45332e6be.png

293ec4d3e2bb6a0.png

作为 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 邮件)。

172da43f310a0579.png

840b494aa5eaa1ef.png

学习内容

  • 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 控制台,然后按以下步骤操作:

  1. 选择您的项目。
  2. 在 Google Cloud 搜索字段中,前往 AI Applications

  1. 查看并同意相关条款后,点击继续并激活 API
  2. 打开 Settings(设置)。
  3. 身份验证标签页中,修改全局

93b0cc6ed63fba0c.png

  1. 选择 Google Identity,然后点击保存

5c01b4cbeebaa93b.png

启用 API

Vertex AI Workspace 数据存储区需要启用以下 API:

  1. Google Cloud 控制台中,启用 Calendar API、Gmail API 和 People API:

3877dcaa56624d0b.png

  1. 依次点击菜单 ☰ > API 和服务 > 已启用的 API 和服务,然后确认列表中包含 Google Calendar APIGmail APIPeople API

创建数据存储区

创建 Google 云端硬盘数据存储区:

  1. 在 Google Cloud 控制台中,前往 AI Applications,然后前往数据存储区

  1. 点击 + 创建数据存储区
  2. 来源中的 Google 云端硬盘下方,点击选择

6939363368bde36d.png

  1. 数据中,选择全部,然后点击继续

5044243322acec9e.png

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

1f5deb1aeecee983.png

  1. 价格中,选择您偏好的定价模式,然后点击创建。在此 Codelab 中,建议使用一般价格
  2. 系统会自动将您重定向到数据存储区,您可以在其中看到新添加的数据存储区。

创建 Google 日历数据存储区:

  1. 点击 + 创建数据存储区
  2. 来源中,搜索 Google 日历,然后点击选择
  3. 操作部分,点击跳过
  4. 配置部分中,将数据连接器名称设置为 calendar
  5. 点击创建
  6. 系统会自动将您重定向到数据存储区,您可以在其中看到新添加的数据存储区。

创建 Google Gmail 数据存储区:

  1. 点击 + 新建数据存储区
  2. 来源中,搜索 Google Gmail,然后点击选择
  3. 操作部分,点击跳过
  4. 配置部分中,将数据连接器名称设置为 gmail
  5. 点击创建
  6. 系统会自动将您重定向到数据存储区,您可以在其中看到新添加的数据存储区。

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 之前,先对其进行验证。

查看解决方案架构

1f337dc91da74391.png

创建应用

创建新的搜索应用以锚定数据存储区。

在 Cloud 控制台中打开 AI Applications > Apps,然后按以下步骤操作:

  1. 点击 + 创建应用
  2. 类型下,点击自定义搜索(常规)下的创建

9714a5fff49b5e1b.png

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

327702cd837cbb18.png

  1. 数据中,选择数据存储区 drivegmailcalendar,然后点击继续

5745607f3c43d5c0.png

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

d53ed9d9d1ced955.png

配置 Web widget

配置搜索微件的视觉外观和行为。

  1. 前往配置
  2. 界面标签页中,将搜索类型设置为支持后续问题的搜索,然后点击保存并发布

af1ca3bd78e1cb4f.png

试用应用

直接在 Google Cloud 控制台中测试搜索应用。

  1. 前往预览,系统会显示 Web widget。
  2. 在聊天中,输入 Do I have any meetings today?,然后按 enter
  3. 在聊天中,输入 Did I receive an email on March 1st 2026?,然后按 enter
  4. 在聊天中,输入 Give me the title of the latest Drive file I created,然后按 enter

d276ff8e2b9d0ddf.png

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 随附的内置开发界面,可简化开发和调试流程。

查看解决方案架构

f14251cca6a19b1f.png

查看源代码

agent.py

以下代码可用于向 Vertex AI 进行身份验证、初始化 Vertex AI Search MCP 和 Chat API 工具,以及定义代理的行为。

  1. 身份验证:它从环境变量中检索 ACCESS_TOKEN,以对 MCP 和 API 调用进行身份验证。
  2. 工具设置:它会初始化 vertexai_mcp(一种连接到 Vertex AI Search Model Context Protocol (MCP) 服务器的工具集)和 send_direct_message 工具。这样,代理便能够搜索您关联的数据存储区并发送 Google Chat 消息。
  3. 智能体定义:它使用 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)]
)

下载源代码

将示例代码下载到本地环境,即可开始使用。

  1. 下载此 GitHub 代码库

  1. 在终端中,打开 solutions/enterprise-ai-agent-local 目录。

启用 API

此解决方案需要启用其他 API:

  1. Google Cloud 控制台中,启用 Vertex AI、Cloud Resource Manager 和 Google Chat API:

60bae4065338c5bf.png

  1. 依次点击菜单 ☰ > API 和服务 > 已启用的 API 和服务,然后确认列表中包含 Vertex AI APICloud Resource Manager APIGoogle Chat API

此解决方案需要配置权限请求页面:

  1. Google Cloud 控制台中,依次点击菜单 ☰ > Google Auth Platform > 品牌推广

  1. 点击开始使用
  2. 应用信息下,将应用名称设置为 Codelab
  3. 用户支持电子邮件中,选择一个支持电子邮件地址,以便用户在对自己的同意情况有疑问时与您联系。
  4. 点击下一步
  5. 受众下,选择内部
  6. 点击下一步
  7. 联系信息下,输入一个电子邮件地址,以便您接收有关项目变更的通知。
  8. 点击下一步
  9. 完成部分,查看 Google API 服务用户数据政策,如果您同意该政策,请选择我同意 Google API 服务:用户数据政策
  10. 依次点击继续创建

bb53eeb45c51d301.png

  1. 系统会保存配置,并自动将您重定向到 Google Auth Platform > 概览

如需了解详情,请参阅完整的配置 OAuth 权限请求指南。

创建 OAuth 客户端凭据

创建新的桌面应用 OAuth 客户端,以便在本地环境中对用户进行身份验证:

  1. Google Cloud 控制台中,依次点击菜单 ☰ > Google Auth 平台 > 客户端

  1. 点击 + 创建客户端
  2. 对于应用类型,选择桌面应用
  3. 名称设置为 codelab
  4. 点击创建。系统会显示新创建的凭据。
  5. 点击下载 JSON,然后将文件另存为 solutions/enterprise-ai-agent-local 目录中的 client_secret.json

c1c9bc2f8c14dd6c.png

启用 Vertex AI Search MCP

  1. 在终端中,执行以下命令:
gcloud beta services mcp enable discoveryengine.googleapis.com \
     --project=$(gcloud config get-value project)

配置 Chat 应用

配置 Google Chat 应用的基本信息详情。

  1. Google Cloud 控制台中,在 Google Cloud 搜索字段中搜索 Google Chat API,点击 Google Chat API,然后依次点击管理配置

  1. 应用名称说明设置为 Vertex AI
  2. 头像网址设置为 https://developers.google.com/workspace/add-ons/images/quickstart-app-avatar.png
  3. 取消选中启用互动功能,然后在随即显示的模态对话框中点击停用
  4. 选择将错误记录到 Logging
  5. 点击保存

952e7ebcb945f1b2.png

在 ADK Web 中运行代理

使用 ADK 网页界面在本地启动智能体。

  1. 在终端中,打开 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

95fc30883ce3d56f.png

试用代理

通过与自定义代理聊天来验证流程。

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

145f47f45332e6be.png

293ec4d3e2bb6a0.png

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 应用中启用快捷操作,从而提升用户体验。

查看解决方案架构

f2fd048ba298f431.png

查看源代码

Agent

agent.py

以下代码可用于向 Vertex AI 进行身份验证、初始化 Vertex AI Search MCP 和 Chat API 工具,以及定义代理的行为。

  1. 身份验证:它使用辅助函数 _get_access_token_from_context 来检索客户端注入的身份验证令牌 (CLIENT_AUTH_NAME)。此令牌对于安全调用下游服务(例如 Vertex AI Search MCP 和 Google Chat 工具)至关重要。
  2. 工具设置:它会初始化 vertexai_mcp(一种连接到 Vertex AI Search Model Context Protocol (MCP) 服务器的工具集)和 send_direct_message 工具。这样,代理便能够搜索您关联的数据存储区并发送 Google Chat 消息。
  3. 智能体定义:它使用 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

以下配置定义了插件的触发器和权限。

  1. 定义插件:告知 Workspace 此项目是 ChatGmail 的插件。
  2. 情境触发器:对于 Gmail,它会设置一个 contextualTrigger,每当用户打开电子邮件时,该 contextualTrigger 就会触发 onAddonEvent。这样,该插件就可以“看到”电子邮件内容。
  3. 权限:其中列出了插件运行所需的 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 消息。

  1. 接收消息onMessage 函数是消息互动的入口点。
  2. 管理上下文:它会将 space.name(即 Chat 空间的 ID)保存到用户的属性中。这样可确保当代理准备好回复时,它能准确知道要将消息发布到哪个对话中。
  3. 委托给代理:它会调用 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 边栏并捕获电子邮件上下文。

  1. 构建界面createSidebarCard 使用 Workspace 卡片服务构建直观的界面。它会创建一个包含文本输入区域和“发送消息”按钮的简单布局。
  2. 捕获电子邮件上下文:在 handleSendMessage 中,代码会检查用户当前是否正在查看电子邮件 (event.gmail.messageId)。如果用户正在查看电子邮件,代码会安全地提取电子邮件的主题和正文,并将其附加到用户的提示中。
  3. 显示结果:代理做出回答后,代码会更新边栏卡片以显示回答。
...
// 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 调用。

  1. 编排 API 调用queryAgent 是插件与 Vertex AI Agent Engine 之间的桥梁。它会构建一个请求,其中包含用户的查询和状态中的身份验证令牌。
  2. 流式传输回答:由于代理回答可能需要一段时间,因此它使用 streamQuery API 和服务器发送的事件 (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 中部署代理

  1. 在终端中,打开上一步下载的源代码中的 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

eafd2f9c4fbf305.png

  1. 当您在日志中看到“正在部署到代理引擎...”这一行时,请打开新终端并执行以下命令,以向 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"
  1. 等待 adk deploy 命令完成,然后从命令输出中以绿色显示的已部署的新智能体的资源名称。

d098fe1347d6581b.png

启动服务账号

创建一个专用服务账号,以授权插件的服务器端操作。

Google Cloud 控制台中,按以下步骤操作:

  1. 依次点击菜单 ☰ > IAM 和管理 > 服务账号 > + 创建服务账号

  1. 服务账号名称设置为 vertexai-add-on

46be0eb53f416c59.png

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

f002fef61c71ed8.png

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

7b140535d9e1af44.png

  1. 对话框随即关闭,新创建的公钥/私钥对将以 JSON 文件的形式自动下载到本地环境。

创建和配置 Apps 脚本项目

创建新的 Apps 脚本项目以托管插件代码,并配置其连接属性。

  1. 点击以下按钮,打开 Enterprise AI 加购项 Apps 脚本项目:

  1. 依次点击概览 > 复制
  2. 在您的 Apps 脚本项目中,依次点击项目设置 > 修改脚本属性 > 添加脚本属性,以添加脚本属性。
  3. REASONING_ENGINE_RESOURCE_NAME 设置为在之前的步骤中复制的 Vertex AI 代理资源名称。其格式如下:
projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ID>
  1. APP_SERVICE_ACCOUNT_KEY 设置为在之前的步骤中下载的服务账号文件中的 JSON 密钥。
  2. 点击保存脚本属性

部署到 Gmail 和 Chat

部署该插件,以便直接在 Gmail 和 Google Chat 中对其进行测试。

在您的 Apps 脚本项目中,按以下步骤操作:

  1. 依次点击部署 > 测试部署,然后点击安装。现在,您可以在 Gmail 中使用此功能。
  2. 点击主要部署 ID 下方的复制

b0cba69eef271850.png

Google Cloud 控制台中,按以下步骤操作:

  1. 在 Google Cloud 搜索字段中搜索 Google Chat API,点击 Google Chat API,然后依次点击管理配置

  1. 选择启用互动功能
  2. 取消选择加入聊天室和群组对话
  3. 连接设置下,选择 Apps 脚本
  4. 部署 ID 设置为在上一步中复制的头部部署 ID
  5. 公开范围下,选择面向您 Workspace 网域中的特定人员和群组提供此 Chat 扩展应用,然后输入您的电子邮件地址。
  6. 点击保存

6ea187ccb90a0e49.png

试用加购项

与实时插件互动,验证它是否可以获取数据并回答情境化问题。

在新标签页中打开 Google Chat,然后按以下步骤操作:

  1. 打开与聊天应用 Vertex AI 的私信对话。

495632314dec5a5d.png

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

172da43f310a0579.png

在新标签页中打开 Gmail,然后按以下步骤操作:

  1. 给自己发送一封电子邮件,其中主题设置为 We need to talk正文设置为 Are you available today between 8 and 9 AM?
  2. 打开新收到的电子邮件。
  3. 打开 Vertex AI 插件边栏。
  4. 消息设置为 Do I have any meeting conflicts?
  5. 点击发送讯息
  6. 答案会显示在按钮下方。

840b494aa5eaa1ef.png

6. 清理

删除 Google Cloud 项目

为避免系统因此 Codelab 中使用的资源向您的 Google Cloud 账号收取费用,我们建议您删除该 Google Cloud 项目。

Google Cloud 控制台中,按以下步骤操作:

  1. 依次点击菜单 ☰ > IAM 和管理 > 设置

  1. 点击关停
  2. 输入项目 ID。
  3. 点击仍要关停

3b9492d97f771b2c.png

7. 恭喜

恭喜!您构建的解决方案可让 Vertex AI 和 Google Workspace 紧密集成,从而为工作人员提供助力!

后续操作

在此 Codelab 中,我们仅展示了最典型的用例,但您或许需要在解决方案中考虑很多值得扩展的地方,例如:

  • 使用 Gemini CLI 和 Antigravity 等 AI 赋能的开发者工具。
  • 与其他代理框架和工具集成,例如自定义 MCP、自定义函数调用和生成式界面。
  • 与在 Vertex AI 等专用平台上托管的其他 AI 模型(包括自定义模型)集成。
  • 与托管在专用平台(例如 Dialogflow)中或由第三方通过 Cloud Marketplace 托管的其他代理集成。
  • 在 Cloud Marketplace 上发布代理,赋能团队、组织或公共用户。

了解详情

我们为开发者提供了大量资源,例如 YouTube 视频、文档网站、代码示例和教程: