將 Gemini Enterprise 代理程式與 Google Workspace 整合

1. 事前準備

83e1c1629d14fb31.png

什麼是 Gemini Enterprise?

Gemini Enterprise 是進階代理平台,可讓全體員工在各種工作流程中,充分運用 Google AI 的優勢。團隊可以在同一個安全環境中探索、建立、共用及執行 AI 代理。

  • 存取進階模型:使用者可立即存取 Google 最強大的多模態 AI (包括 Gemini),解決複雜的業務難題。
  • 運用專用代理:這套工具內含 Google 代理,可直接用於研究、編碼和筆記,立即發揮價值。
  • 為每位員工提供強大功能:無程式碼和專業程式碼選項可讓各部門員工建構及管理自己的自訂代理,自動執行工作流程。
  • 依據資料建立虛擬服務專員:虛擬服務專員可安全地連結至公司內部資料和第三方應用程式,確保回覆內容符合情境。
  • 集中式管理:管理員可以查看及稽核所有代理活動,確保機構符合嚴格的安全和法規遵循標準。
  • 透過生態系統擴展:這個平台與廣大的合作夥伴應用程式和服務供應商網路整合,可跨不同系統擴展自動化功能。

127f2ed7d484722c.png

什麼是 Google Workspace?

Google Workspace 是一套雲端式效率提升與協作解決方案,適用於個人、學校和企業:

  • 通訊:專業電子郵件服務 (Gmail)、視訊會議 (Meet) 和團隊訊息 (Chat)。
  • 內容創作:撰寫文件 (Google 文件)、建立試算表 (Google 試算表) 和設計簡報 (Google 簡報) 的工具。
  • 整理:共用日曆 (日曆) 和數位筆記 (Keep)。
  • 儲存空間:集中式雲端空間,可安全地儲存及共用檔案 (雲端硬碟)。
  • 管理:管理控制項,可管理使用者和安全性設定 (Workspace 管理控制台)。

哪些類型的自訂整合?

Google Workspace 和 Gemini Enterprise 形成強大的回饋迴路,Workspace 提供即時資料和協作背景資訊,Gemini Enterprise 則提供模型、代理式推理和協調功能,自動執行智慧型工作流程。

  • 智慧連線:透過 Google 管理的資料儲存空間、API 和 MCP 伺服器 (Google 管理和自訂),代理程式可以安全且順暢地存取 Workspace 資料,並代表使用者採取行動。
  • 自訂代理:團隊可使用無程式碼設計工具或專業程式碼架構,根據管理員控管的 Workspace 資料和動作,建構專用代理。
  • 原生整合:無論是透過專屬 UI 元件或背景程序,Workspace 外掛程式都能彌合 AI 系統與 Chat 和 Gmail 等應用程式之間的差距。代理人可直接在使用者所在位置提供即時協助,並根據情境提供支援。

Google Workspace 擁有強大的生產力生態系統,Gemini Enterprise 則具備先進的代理功能,兩者結合後,機構就能透過自訂的資料導向 AI 代理,在團隊每天使用的工具中自動執行複雜的工作流程,進而改變營運方式。

必要條件

如要在自己的環境中完成所有步驟,您需要:

建構目標

在本程式碼研究室中,我們將使用 Gemini Enterprise AI 代理,建構與 Google Workspace 緊密整合的三種解決方案。他們將展示可用於與資料、動作和 UI 互動的架構模式。

無程式碼自訂代理

使用者可以透過自然語言,讓這個代理程式搜尋資料及執行 Workspace 動作。這項功能需要下列元素:

  • 模型:Gemini。
  • 資料和動作:Google Workspace 的 Gemini Enterprise 資料儲存庫 (日曆、Gmail、雲端硬碟、NotebookLM)、Google 搜尋
  • 代理建構工具:Gemini Enterprise 代理設計工具。
  • 代理主機:Gemini Enterprise。
  • 使用者介面:Gemini Enterprise 網頁應用程式。

90e42539e5959634.png

60e62437ce29a818.png

專業程式碼自訂代理

使用者可以透過自訂工具和規則,以自然語言搜尋資料及執行 Workspace 動作。這項功能需要下列元素:

  • 模型:Gemini。
  • 資料和動作:Google Workspace 的 Gemini Enterprise 資料存放區 (日曆、Gmail、雲端硬碟、NotebookLM)、Google 搜尋、Google 管理的 Vertex AI Search Model Context Protocol (MCP) 伺服器、傳送 Google Chat 訊息的自訂工具函式 (透過 Google Chat API)。
  • 代理建構工具:Agent Development Kit (ADK)。
  • 代理主機:Vertex AI Agent Engine。
  • 使用者介面:Gemini Enterprise 網頁應用程式。

1647ebff031c42e7.png

a8087d2351e77fb4.png

預設代理程式為 Google Workspace 外掛程式

使用者可以在 Workspace 應用程式 UI 中,以自然語言搜尋 Workspace 資料。這項功能需要下列元素:

  • 模型:Gemini。
  • 資料:Google Workspace 的 Gemini Enterprise 資料儲存空間 (日曆、Gmail、雲端硬碟、NotebookLM)、Google 搜尋
  • 代理主機:Gemini Enterprise。
  • 使用者介面:適用於 Chat 和 Gmail 的 Google Workspace 外掛程式 (可輕鬆擴充至 Google 日曆、雲端硬碟、文件、試算表和簡報)。
  • Google Workspace 外掛程式:Apps Script、Gemini Enterprise 和 Vertex AI API、情境 (使用者中繼資料、選取的 Gmail 郵件)。

c8c63fb3f324fecf.png

d33b8cb50ee251b7.png

學習目標

  • Gemini Enterprise 和 Google Workspace 之間的整合點,可啟用資料和動作。
  • 無程式碼和專業程式碼選項,可建構 Gemini Enterprise 代管的自訂代理。
  • 使用者可透過 Gemini Enterprise 網頁應用程式和 Google Workspace 應用程式存取代理程式。

2. 做好準備

查看概念

Gemini Enterprise 應用程式

Gemini Enterprise 應用程式會向使用者提供搜尋結果、動作和代理。在 API 的情境中,「應用程式」一詞可與「引擎」互換使用。應用程式必須連結至資料儲存庫,才能使用其中的資料提供搜尋結果、答案或動作。

Gemini Enterprise 網頁應用程式

Gemini Enterprise 網頁應用程式會與 Gemini Enterprise 應用程式建立關聯,做為集中式 AI 基地,員工可透過單一即時通訊介面搜尋公司資料孤島、執行專門的 AI 代理來處理複雜工作流程,以及生成企業級隱私權保護的專業內容。

初始化及存取資源

在本節中,您將透過偏好的網頁瀏覽器存取及設定下列資源。

Gemini Enterprise 應用程式

在新分頁中開啟 Google Cloud 控制台,然後按照下列步驟操作:

  1. 選取專案。
  2. 在 Google Cloud 搜尋欄位中,搜尋並選取「Gemini Enterprise」,然後按一下「+ 建立應用程式」。如果沒有 Gemini Enterprise 授權,系統會提示你啟用 30 天免費試用授權。

  1. 將「App name」(應用程式名稱) 設為 codelab
  2. 系統會根據名稱產生 ID,並顯示在欄位下方,請複製該 ID。
  3. 將「多區域」設為 global (Global)
  4. 點選「建立」

8712ada39377205e.png

  1. 應用程式建立完成後,系統會自動將您重新導向至「Gemini Enterprise」 >「總覽」
  2. 按一下「取得完整存取權」下方的「設定身分」
  3. 在新畫面中,選取「使用 Google Identity」,然後點按「確認員工身分」

3209c156eff4ba43.png

  1. 系統會儲存設定,並自動將您重新導向至「Gemini Enterprise」>「總覽」
  2. 前往「設定」
  3. 在「功能管理」分頁中,開啟「啟用代理程式設計工具」,然後按一下「儲存」

f0cd9da419b41cb6.png

Gemini Enterprise 網頁應用程式

在新分頁中從 Cloud 控制台開啟 Gemini Enterprise,然後按照下列步驟操作:

  1. 按一下名為 codelab 的應用程式。
  2. 複製顯示的網址,我們會在後續步驟中使用該網址前往 Gemini Enterprise 網頁應用程式。

b46ee6176744565d.png

3. 無程式碼自訂代理

使用者可以透過自然語言,讓這個代理程式搜尋資料及執行 Workspace 動作。這項功能需要下列元素:

  • 模型:Gemini。
  • 資料和動作:Google Workspace 的 Gemini Enterprise 資料儲存庫 (日曆、Gmail、雲端硬碟、NotebookLM)、Google 搜尋
  • 代理建構工具:Gemini Enterprise 代理設計工具。
  • 代理主機:Gemini Enterprise。
  • 使用者介面:Gemini Enterprise 網頁應用程式。

查看概念

Gemini

Gemini 是 Google 開發的多模態 LLM,這項技術可協助使用者發揮潛能,激發想像力、拓展好奇心,以及提升工作效率。

Gemini Enterprise 資料儲存庫

Gemini Enterprise 資料存放區是一種實體,內含從第一方資料來源 (例如 Google Workspace) 或第三方應用程式 (例如 Jira 或 Salesforce) 擷取的資料。含有第三方應用程式資料的資料儲存庫也稱為資料連接器。

Gemini Enterprise 代理設計工具

Gemini Enterprise 代理設計工具是互動式無程式碼/低程式碼平台,可讓您在 Gemini Enterprise 中建立、管理及發布單一和多步驟代理。

查看解決方案架構

e77aafb772502aaf.png

啟用 API

如要使用 Gemini Enterprise Workspace 資料儲存庫,必須啟用下列 API:

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

573322606b715a69.png

  1. 依序點選「選單」圖示 ☰ >「API 和服務」>「已啟用的 API 和服務」,然後確認「Google Calendar API」、「Gmail API」和「People API」是否在清單中。

Gemini Enterprise Workspace 日曆和 Gmail 動作需要設定同意畫面:

  1. Google Cloud 控制台中,依序點選「選單」圖示 >「Google Auth platform」(Google 驗證平台) >「Branding」(品牌)

  1. 按一下 [開始使用]。
  2. 在「應用程式資訊」下方,將「應用程式名稱」設為 Codelab
  3. 在「使用者支援電子郵件」中,選擇支援電子郵件地址,方便使用者在同意聲明方面有任何疑問時與您聯絡。
  4. 點選 [下一步]。
  5. 在「目標對象」下方,選取「內部」
  6. 點選 [下一步]。
  7. 在「聯絡資訊」下方,輸入可接收專案異動通知的電子郵件地址
  8. 點選 [下一步]。
  9. 在「完成」部分,請詳閱《Google API 服務使用者資料政策》,然後選取「我同意《Google API 服務:使用者資料政策》」
  10. 依序點選「繼續」和「建立」

578c2b38219b2f7b.png

  1. 系統會儲存設定,並自動將您重新導向至 Google Auth Platform > Overview
  2. 前往「資料存取權」
  3. 按一下「新增或移除範圍」
  4. 複製下列範圍,並貼到「手動新增範圍」欄位。
https://www.googleapis.com/auth/calendar.readonly
https://www.googleapis.com/auth/calendar.events
https://www.googleapis.com/auth/calendar.calendars
https://www.googleapis.com/auth/gmail.send
https://www.googleapis.com/auth/gmail.readonly
  1. 依序點選「新增至表格」、「更新」和「儲存」

874b1dda14e8f379.png

詳情請參閱完整的「設定 OAuth 同意畫面」指南。

建立 OAuth 用戶端憑證

為 Gemini Enterprise 建立新的 OAuth 用戶端,以驗證使用者:

  1. Google Cloud 控制台中,依序點選「選單」圖示 >「Google Auth platform」 >「Clients」

  1. 按一下「+ 建立用戶端」
  2. 「應用程式類型」請選取「網頁應用程式」
  3. 將「Name」(名稱) 設為 codelab
  4. 略過「已授權的 JavaScript 來源」
  5. 在「已授權的重新導向 URI」部分,按一下「新增 URI」,然後輸入 https://vertexaisearch.cloud.google.com/oauth-redirect
  6. 點選「建立」
  7. 畫面上會顯示一個對話方塊,其中包含新建立的 OAuth 用戶端 ID 和密鑰。請將這項資訊儲存在安全的地方。

a46e5ebfb851aea5.png

建立資料儲存庫

在新分頁中從 Cloud 控制台開啟 Gemini Enterprise,然後按照下列步驟操作:

  1. 按一下名為 codelab 的應用程式。
  2. 點按導覽選單中的「已連結的資料儲存庫」
  3. 點選「+ 新增資料儲存庫」
  4. 在「來源」中搜尋「Google 日曆」,然後按一下「選取」
  5. 在「動作」部分,輸入先前步驟中儲存的用戶端 ID用戶端密鑰,然後點選「確認你的身分」,並按照步驟驗證及授權 OAuth 用戶端。
  6. 啟用「建立日曆活動」和「更新日曆活動」動作。
  7. 按一下「繼續」

a1d76e70edec0cf.png

  1. 在「設定」部分,將「資料連接器名稱」設為 calendar
  2. 點選「建立」
  3. 系統會自動將您重新導向至「已連結的資料儲存庫」,您可以在這裡查看新加入的資料儲存庫。

建立 Google Gmail 資料儲存庫:

  1. 點選「+ 新增資料儲存庫」
  2. 在「Source」(來源) 中搜尋「Google Gmail」,然後按一下「Select」(選取)
  3. 在「動作」部分中,輸入先前步驟儲存的「用戶端 ID」和「用戶端密鑰」,然後點選「確認你的身分」
  4. 啟用「傳送電子郵件」動作。
  5. 按一下「繼續」
  6. 在「設定」部分,將「資料連接器名稱」設為 gmail
  7. 點選「建立」
  8. 系統會自動將您重新導向至「已連結的資料儲存庫」,您可以在這裡查看新加入的資料儲存庫。

建立 Google 雲端硬碟資料儲存庫:

  1. 點選「+ 新增資料儲存庫」
  2. 在「Source」(來源) 中搜尋「Google Drive」(Google 雲端硬碟),然後按一下「Select」(選取)
  3. 在「資料」部分,選取「全部」,然後按一下「繼續」
  4. 在「設定」部分,將「資料連接器名稱」設為 drive
  5. 點選「建立」
  6. 系統會自動將您重新導向至「已連結的資料儲存庫」,您可以在這裡查看新加入的資料儲存庫。

建立 NotebookLM 資料儲存庫:

  1. 點選「+ 新增資料儲存庫」
  2. 在「來源」中搜尋「NotebookLM」,然後按一下「選取」
  3. 在「設定」部分,將「資料連接器名稱」設為 notebooklm
  4. 點選「建立」
  5. 系統會自動將您重新導向至「已連結的資料儲存庫」,您可以在這裡查看新加入的資料儲存庫。

幾分鐘後,所有已連結資料儲存庫的狀態 (NotebookLM 除外) 都會顯示為「Active」(運作中)。如果看到任何錯誤,可以點選資料來源來查看錯誤詳細資料。

ceba9eb2480a2696.png

測試資料儲存庫

開啟先前複製的 Gemini Enterprise 網頁應用程式網址:

  1. 依序點選「選單」圖示 ☰ >「發起新即時通訊」
  2. 在新的即時通訊訊息欄位頁尾,按一下「連接器」圖示,然後啟用所有連接器。
  3. 您現在可以試用與連結器相關的提示。舉例來說,在對話中輸入 Do I have any meetings today?,然後按 enter
  4. 接著輸入 How many emails did I receive today? 並按下 enter 鍵。
  5. 最後輸入 Give me the title of the last Drive file I created 並按下 enter 鍵。

90e42539e5959634.png

建立自訂代理

在 Gemini Enterprise 網頁應用程式中,使用 Agent Designer 建立新代理:

  1. 依序點選「選單」圖示 ☰ >「+ 新增代理程式」
  2. 在對話中輸入 An agent that always sends pirate-themed emails but use normal English otherwise,然後按下 enter 鍵。

2803c1dedd20433e.png

  1. Agent Designer 會根據提示草擬代理程式,並在編輯器中開啟。
  2. 按一下「Create」(建立)

試用自訂代理

  1. 在 Gemini Enterprise 網頁應用程式中,與新建立的代理程式對話:
  2. 依序點選「選單」圖示 ☰ >「代理程式」
  3. 在「你的代理程式」下方選取代理程式。
  4. 在新的即時通訊訊息欄位頁尾,按一下「連結器」圖示,然後按一下「啟用動作」,為「郵件」授權代理程式
  5. 在對話中輸入 Send an email to someone@example.com saying I'll see them at Cloud Next, generate some subject and body yourself,然後按下 enter 鍵。您可以將範例電子郵件地址替換為自己的電子郵件地址。
  6. 按一下「✔️」即可傳送電子郵件。

60e62437ce29a818.png

d4fb65d14fdf27da.png

4. 專業程式碼自訂代理

使用者可以透過自訂工具和規則,以自然語言搜尋資料及執行 Workspace 動作。這項功能需要下列元素:

  • 模型:Gemini。
  • 資料和動作:Google Workspace 的 Gemini Enterprise 資料存放區 (日曆、Gmail、雲端硬碟、NotebookLM)、Google 搜尋、Google 管理的 Vertex AI Search Model Context Protocol (MCP) 伺服器、傳送 Google Chat 訊息的自訂工具函式 (透過 Google Chat API)。
  • 代理建構工具:Agent Development Kit (ADK)。
  • 代理主機:Vertex AI Agent Engine。
  • 使用者介面:Gemini Enterprise 網頁應用程式。

這項功能會透過自備功能整合至 Gemini Enterprise,因此我們需要完成部署、註冊及設定步驟。

查看概念

Vertex AI

Vertex AI 提供建構及使用生成式 AI 所需的一切資源,包括 AI 解決方案、搜尋和對話、超過 130 個基礎模型,以及整合式 AI 平台。

4670fcf7a826af4d.png

Agent Development Kit (ADK)

Agent Development Kit (ADK) 是一套專用工具和架構,提供預先建構的模組,可用於推理、記憶體管理和工具整合,簡化自主式 AI 代理的建立程序。

模型情境通訊協定 (MCP)

Model Context Protocol (MCP) 是一項開放標準,旨在透過通用「隨插即用」介面,讓 AI 應用程式與各種資料來源或工具順暢安全地整合。

函式工具

函式工具是預先定義的可執行常式,AI 模型可觸發這項工具來執行特定動作,或從外部系統擷取即時資料,擴充功能,不只能生成簡單的文字。

查看解決方案架構

43df337e0f3d64e8.png

檢查原始碼

agent.py

...
MODEL = "gemini-2.5-flash"

# Gemini Enterprise authentication injects a bearer token into the ToolContext state.
# The key pattern is "GE_AUTH_NAME_<random_digits>".
# We dynamically parse this token to authenticate our MCP and API calls.
GE_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(GE_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)]
)

啟用 API

這個解決方案需要啟用其他 API:

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

4f02a36b050bab00.png

  1. 依序點選「選單」圖示 ☰ >「API 和服務」>「已啟用的 API 和服務」,然後確認清單中是否列出「Vertex AI API」、「Cloud Resource Manager API」和「Google Chat API」。

解決方案需要額外資料存取權:

  1. Google Cloud 控制台中,依序點選「選單」圖示 >「Google Auth platform」(Google 驗證平台) >「Data Access」(資料存取權)

  1. 按一下「新增或移除範圍」
  2. 複製下列範圍,並貼到「手動新增範圍」欄位。
  3. 依序點選「新增至表格」、「更新」和「儲存」
https://www.googleapis.com/auth/cloud-platform
https://www.googleapis.com/auth/chat.messages.create
https://www.googleapis.com/auth/chat.spaces.create
  1. 依序點選「新增至表格」、「更新」和「儲存」

56fbba733139acfe.png

更新 OAuth 用戶端憑證

解決方案需要額外的授權重新導向 URI:

  1. Google Cloud 控制台中,依序點選「選單」圖示 >「Google Auth platform」 >「Clients」

  1. 按一下客戶名稱 codelab
  2. 在「已授權的重新導向 URI」部分,按一下「新增 URI」,然後輸入 https://vertexaisearch.cloud.google.com/static/oauth/oauth.html
  3. 按一下 [儲存]

deed597aa54fec91.png

啟用 Vertex AI Search MCP

  1. 在終端機中執行:
gcloud beta services mcp enable discoveryengine.googleapis.com \
     --project=$(gcloud config get-value project)

設定 Chat 擴充應用程式

  1. Google Cloud 控制台的 Google Cloud 搜尋欄位中搜尋 Google Chat API,然後依序點選「Google Chat API」、「管理」和「設定」

  1. 將「應用程式名稱」和「說明」設為 Gemini Enterprise
  2. 將「Avatar URL」(顯示圖片網址) 設為 https://developers.google.com/workspace/add-ons/images/quickstart-app-avatar.png
  3. 取消選取「啟用互動功能」,然後在隨即顯示的模式對話方塊中按一下「停用」
  4. 選取「將錯誤記錄至 Logging」
  5. 按一下 [儲存]

90cb612e51bce4e6.png

在 Vertex AI Agent Engine 中部署代理程式

  1. 下載這個 GitHub 存放區

  1. 在終端機中開啟 solutions/enterprise-ai-agent 目錄,然後執行:
# 1. Create and activate a new virtual environment
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. 在記錄中看到「Deploying to agent engine...」這行時,請開啟新的終端機並執行下列指令,將必要權限新增至 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

在 Gemini Enterprise 中註冊代理

在新分頁中從 Cloud 控制台開啟 Gemini Enterprise,然後按照下列步驟操作:

  1. 按一下名為 codelab 的應用程式。
  2. 在導覽選單中,按一下「代理商」
  3. 按一下「+ 新增代理人」
  4. 按一下「透過 Agent Engine 建立的自訂代理」的「新增」。系統隨即會顯示「授權」部分。
  5. 按一下「新增授權」
  6. 將「Authorization name」(授權名稱) 設為 enterprise-ai。系統會根據名稱產生 ID,並顯示在欄位下方,請複製該 ID。
  7. 將「用戶端 ID」設為與先前步驟中建立及更新的 OAuth 用戶端相同的值。
  8. 將「Client secret」設為與先前步驟中建立及更新的 OAuth 用戶端相同的值。
  9. 將「權杖 URI」設為 https://oauth2.googleapis.com/token
  10. 將「授權 URI」設為下列值,並將 <CLIENT_ID> 替換為先前步驟中建立及更新的 OAuth 用戶端 ID。
https://accounts.google.com/o/oauth2/v2/auth?client_id=<CLIENT_ID>&redirect_uri=https%3A%2F%2Fvertexaisearch.cloud.google.com%2Fstatic%2Foauth%2Foauth.html&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.readonly%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.calendars%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.events%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.send%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.readonly%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fchat.messages.create%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fchat.spaces.create&include_granted_scopes=true&response_type=code&access_type=offline&prompt=consent
  1. 依序點選「完成」和「下一步」。畫面上會顯示「Configuration」(設定) 部分。
  2. 將「代理程式名稱」和「代理程式說明」設為 Enterprise AI
  3. 將「Agent Engine reasoning engine」設為先前步驟中複製的 Reasoning Engine 資源名稱。格式如下:
projects/<PROJECT_ID>/locations/<LOCATION>/reasoningEngines/<REASONING_ENGINE_ID>
  1. 按一下「建立」,新增的代理程式現在會列在「代理程式」下方。

試用代理

  1. 在 Gemini Enterprise 網頁應用程式中,與新註冊的代理程式對話:
  2. 依序點選「選單」圖示 ☰ >「代理程式」
  3. 在「來自貴機構」下方選取代理程式。
  4. 在對話中輸入 Please find my meetings for today, I need their titles and links,然後按下 enter 鍵。
  5. 按一下「授權」,然後按照授權流程操作。

ed61cf654cbcd76c.png

  1. 代理程式會根據使用者的帳戶,列出日曆活動。
  2. 在對話中輸入 Please send a Chat message to someone@example.com with the following text: Hello!,然後按下 enter 鍵。
  3. 代理程式會回覆確認訊息。

1647ebff031c42e7.png

a8087d2351e77fb4.png

5. 預設代理程式為 Google Workspace 外掛程式

使用者可以在 Workspace 應用程式 UI 中,以自然語言搜尋 Workspace 資料。這項功能需要下列元素:

  • 模型:Gemini。
  • 資料:Google Workspace 的 Gemini Enterprise 資料儲存空間 (日曆、Gmail、雲端硬碟、NotebookLM)、Google 搜尋
  • 代理主機:Gemini Enterprise。
  • 使用者介面:適用於 Chat 和 Gmail 的 Google Workspace 外掛程式 (可輕鬆擴充至 Google 日曆、雲端硬碟、文件、試算表和簡報)。
  • Google Workspace 外掛程式:Apps Script、Gemini Enterprise 和 Vertex AI API、情境 (使用者中繼資料、選取的 Gmail 郵件)。

Google Workspace 外掛程式會使用 StreamAssist API 連結至 Gemini Enterprise。

複習概念

Google Workspace 外掛程式

Google Workspace 外掛程式是自訂應用程式,可擴充一或多個 Google Workspace 應用程式 (Gmail、Chat、日曆、文件、雲端硬碟、Meet、試算表和簡報)。

Apps Script

Apps Script 是以雲端為基礎的 JavaScript 平台,由 Google 雲端硬碟提供支援,可讓您整合 Google 產品並自動執行相關工作。

Google Workspace Card 架構

Google Workspace 的資訊卡架構可讓開發人員建立豐富的互動式使用者介面。可建構有條理且美觀的卡片,當中可包含文字、圖片、按鈕和其他小工具。這些資訊卡提供結構化資訊,並直接在 Workspace 應用程式中啟用快速動作,進而提升使用者體驗。

檢閱解決方案架構

1798c39f7aaed8fc.png

檢查原始碼

appsscript.json

...
"addOns": {
    "common": {
      "name": "Enterprise 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/discoveryengine.assist.readwrite",
    "https://www.googleapis.com/auth/gmail.addons.execute",
    "https://www.googleapis.com/auth/gmail.addons.current.message.readonly"
  ]
...

Chat.gs

...
// 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

...
// 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 responseText = queryAgent({ text: finalQueryText, forceNewSession: true });

    // 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(responseText);
    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

...
// Service that handles Gemini Enterprise AI Agent operations.

// Submits a query to the AI agent and returns the response string synchronously
function queryAgent(input) {
  const isNewSession = input.forceNewSession || !PropertiesService.getUserProperties().getProperty(AGENT_SESSION_NAME);
  const sessionName = input.forceNewSession ? createAgentSession() : getOrCreateAgentSession();

  let systemPrompt = "SYSTEM PROMPT START Do not respond with tables but use bullet points instead.";
  if (input.forceNewSession) {
    systemPrompt += " Do not ask the user follow-up questions or converse with them as history is not kept in this interface.";
  }
  systemPrompt += " SYSTEM PROMPT END\n\n";

  const queryText = isNewSession ? systemPrompt + input.text : input.text;

  const requestPayload = {
    "session": sessionName,
    "userMetadata": { "timeZone": Session.getScriptTimeZone() },
    "query": { "text": queryText },
    "toolsSpec": { "vertexAiSearchSpec": { "dataStoreSpecs": getAgentDataStores().map(ds => { dataStore: ds }) } },
    "agentsSpec": { "agentSpecs": [{ "agentId": getAgentId() }] }
  };

  const responseContentText = UrlFetchApp.fetch(
    `https://${getLocation()}-discoveryengine.googleapis.com/v1alpha/${getReasoningEngine()}/assistants/default_assistant:streamAssist?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 answerText = "";
  for (const eventJson of events) {
    if (isInDebugMode()) {
      console.log("Event: " + eventJson);
    }
    const event = JSON.parse(eventJson);

    // Ignore internal events
    if (!event.answer) {
      console.log(`Ignored: internal event`);
      continue;
    }

    // Handle text replies
    const replies = event.answer.replies || [];
    for (const reply of replies) {
      const content = reply.groundedContent.content;
      if (content) {
        if (isInDebugMode()) {
          console.log(`Processing content: ${JSON.stringify(content)}`);
        }
        if (content.thought) {
          console.log(`Ignored: thought event`);
          continue;
        }
        answerText += content.text;
      }
    }

    if (event.answer.state === "SUCCEEDED") {
      console.log(`Answer text: ${answerText}`);
      return answerText;
    } else if (event.answer.state !== "IN_PROGRESS") {
      throw new Error("Something went wrong, check the Apps Script logs for more info.");
    }
  }
  return answerText;
}

// Gets the list of data stores configured for the agent to include in the request.
function getAgentDataStores() {
  const responseContentText = UrlFetchApp.fetch(
    `https://${getLocation()}-discoveryengine.googleapis.com/v1/${getReasoningEngine().split('/').slice(0, 6).join('/')}/dataStores`,
    {
      method: 'get',
      // Use the add on service account credentials for data store listing access
      headers: { 'Authorization': `Bearer ${getAddonCredentials().getAccessToken()}` },
      contentType: 'application/json',
      muteHttpExceptions: true
    }
  ).getContentText();
  if (isInDebugMode()) {
    console.log(`Response: ${responseContentText}`);
  }
  const dataStores = JSON.parse(responseContentText).dataStores.map(ds => ds.name);
  if (isInDebugMode()) {
    console.log(`Data stores: ${dataStores}`);
  }
  return dataStores;
}
...

啟動服務帳戶

Google Cloud 控制台中,按照下列步驟操作:

  1. 依序點選「選單 ☰」>「IAM 與管理」>「服務帳戶」>「+ 建立服務帳戶」

  1. 將「服務帳戶名稱」設為 ge-add-on

d44d6aae29e2464c.png

  1. 按一下 [建立並繼續]
  2. 在權限中新增「Discovery Engine 檢視者」角色。

f1374efa4f326ef5.png

  1. 依序點按「繼續」和「完成」。系統會將您重新導向至「服務帳戶」頁面,您可以在該頁面中看到建立的服務帳戶。

b9496085f1404c5c.png

  1. 選取新建立的服務帳戶,然後選取「金鑰」分頁標籤。
  2. 依序點選「新增金鑰」和「建立新的金鑰」
  3. 選取「JSON」,然後按一下「建立」

f4280f5533a08821.png

  1. 對話方塊會關閉,新建立的公開/私密金鑰組會自動下載到本機環境,並儲存為 JSON 檔案。

建立及設定 Apps Script 專案

  1. 點選下列按鈕,開啟 Enterprise AI 外掛程式 Apps Script 專案:

  1. 依序點選「總覽」 >「建立副本」
  2. 在 Apps Script 專案中,依序點選「專案設定」 >「編輯指令碼屬性」 >「新增指令碼屬性」,即可新增指令碼屬性。
  3. REASONING_ENGINE_RESOURCE_NAME 設為 Gemini Enterprise 應用程式資源名稱。格式如下:
# 1. Replace PROJECT_ID with the Google Cloud project ID.
# 2. Replace GE_APP_ID with the codelab app ID found in Google Cloud console > Gemini Enterprise > Apps.

projects/<PROJECT_ID>/locations/global/collections/default_collection/engines/<GE_APP_ID>
  1. APP_SERVICE_ACCOUNT_KEY 設為先前步驟下載的服務帳戶檔案中的 JSON 金鑰。
  2. 按一下「儲存指令碼屬性」

部署至 Gmail 和 Chat

在 Apps Script 專案中,請按照下列步驟操作:

  1. 依序點選「部署」>「測試部署作業」,然後點選「安裝」。這項功能現已在 Gmail 中推出。
  2. 按一下「首要部署作業 ID」下方的「複製」

2ed2df972ad92715.png

Google Cloud 控制台中,按照下列步驟操作:

  1. 在 Google Cloud 搜尋欄位中搜尋 Google Chat API,然後依序點選「Google Chat API」、「管理」和「設定」

  1. 選取「啟用互動功能」
  2. 取消選取「加入聊天室和群組對話」
  3. 在「連線設定」下方,選取「Apps Script」
  4. 將「部署作業 ID」設為在上一個步驟中複製的「首要部署作業 ID」
  5. 在「瀏覽權限」下方,選取「將這個 Chat 擴充應用程式提供給 Your Workspace Domain 中的特定使用者和群組」,然後輸入電子郵件地址。
  6. 按一下 [儲存]

3b7d461c423f7c51.png

試用外掛程式

在新分頁中開啟 Google Chat,然後按照下列步驟操作:

  1. 開啟與 Chat 應用程式 Gemini Enterprise 互傳的即時訊息。

3da8690d19baf2d0.png

  1. 按一下「設定」,然後完成驗證流程。
  2. 輸入 What are my meetings for today?,然後按下 enter 鍵。Gemini Enterprise Chat 應用程式應回覆結果。

c8c63fb3f324fecf.png

在新分頁中開啟 Gmail,然後按照下列步驟操作:

  1. 傳送電子郵件給自己,並將「主旨」設為 We need to talk,將「內文」設為 Are you available today between 8 and 9 AM?
  2. 開啟新收到的電子郵件。
  3. 開啟「Enterprise AI」外掛程式側欄。
  4. 將「Message」(訊息) 設為 Am I?
  5. 按一下 [傳送訊息]。
  6. 答案會顯示在按鈕下方。

d33b8cb50ee251b7.png

6. 清除

刪除 Google Cloud 專案

如要避免系統向您的 Google Cloud 帳戶收取本程式碼研究室所用資源的費用,建議您刪除 Google Cloud 專案。

Google Cloud 控制台中,按照下列步驟操作:

  1. 依序點選「選單」圖示 ☰ >「IAM 與管理」>「設定」

  1. 按一下「Shut down」(關閉)
  2. 輸入專案 ID。
  3. 按一下「仍要關閉」

3b9492d97f771b2c.png

7. 恭喜

恭喜!您打造的解決方案充分發揮 Gemini Enterprise 和 Google Workspace 的整合效益,協助工作者提升效率!

後續步驟

本程式碼研究室只展示最典型的用途,但您可能想在解決方案中考慮許多擴充領域,例如:

  • 使用 Gemini CLI 和 Antigravity 等 AI 輔助開發人員工具。
  • 與其他代理架構和工具整合,例如自訂 MCP、自訂函式呼叫和生成式 UI。
  • 與其他 AI 模型整合,包括託管於 Vertex AI 等專用平台的自訂模型。
  • 與其他代理程式整合,這些代理程式可託管在 Dialogflow 等專用平台,或透過 Cloud Marketplace 由第三方託管。
  • 在 Cloud Marketplace 發布代理,讓團隊、機構或公開使用者都能使用。

瞭解詳情

開發人員可參考許多資源,例如 YouTube 影片、說明文件網站、程式碼範例和教學課程: