Skip to content

Commit 9f6332f

Browse files
PierrickVouletpierrick
andauthored
feat: add Enteprise AI chat solution (#2620)
* feat: add Enteprise AI chat solution * fix: rename agent folder * fix: typo --------- Co-authored-by: pierrick <pierrick@google.com>
1 parent 6e73143 commit 9f6332f

File tree

6 files changed

+5979
-0
lines changed

6 files changed

+5979
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Enterprise AI Agent (built as Gemini Enterprise BYO Agent)
2+
3+
**Note:** This project is part of an official Google Codelab ([link pending](#)).
4+
5+
This sample contains a specialized Gemini Enterprise Agent built using the Google Agent Development Kit (ADK). This agent acts as an Enterprise AI Assistant by querying user's data corpus using the Vertex AI Search MCP toolset and sending Chat messages to DM spaces using a custom Function tool & Google Chat API.
6+
7+
## Key Features
8+
9+
1. **Dynamic Vertex AI Serving Configs:**
10+
The agent automatically discovers your project's `default_collection` engine and dynamically binds its queries to the `default_serving_config`.
11+
12+
2. **Dynamic Authentication (`ToolContext`):**
13+
When deployed as a Bring-Your-Own (BYO) model via Gemini Enterprise, the session state dynamically passes an authentication token (e.g., `enterprise-ai_12345`). This agent intercepts the `ToolContext` state and extracts the token at runtime using regex pattern matching (`^enterprise-ai_\d+$`) to securely execute calls using a Bearer token.
14+
15+
3. **Graceful Timeouts:**
16+
The `McpToolset` streaming components have been intentionally configured with an explicit 15-second `timeout` and `sse_read_timeout` to prevent the agent from hanging infinitely on backend network issues.
17+
18+
4. **Google Chat Integration:**
19+
The agent natively includes a `send_direct_message` tool powered by the `google-apps-chat` SDK. This allows the AI to immediately send direct messages to users inside Google Chat. It seamlessly reuses the same authentication token extracted from the `ToolContext` used for Vertex AI.
20+
21+
## Deployment
22+
23+
This agent is designed exclusively to be deployed as a backend for a Gemini Enterprise (GE) Bring-Your-Own (BYO) Agent. It **will not** work successfully if tested locally via standard ADK run commands because it relies entirely on the external GE gateway to dynamically inject OAuth tokens into the `ToolContext` at runtime.
24+
25+
Deploy this agent directly to Vertex AI Agent Engines using the ADK CLI:
26+
27+
```bash
28+
adk deploy agent_engine \
29+
--project=your-gcp-project-id \
30+
--region=us-central1 \
31+
--display_name="Enterprise AI" \
32+
enterprise_ai
33+
```
34+
35+
[Register](https://docs.cloud.google.com/gemini/enterprise/docs/register-and-manage-an-adk-agent) the deployed agent in the Gemini Enterprise UI as a BYO agent.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from . import agent
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import re
16+
import google.auth
17+
from dotenv import load_dotenv
18+
load_dotenv()
19+
20+
from google.cloud import discoveryengine_v1
21+
from google.adk.agents.llm_agent import LlmAgent
22+
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset, StreamableHTTPConnectionParams
23+
from google.adk.tools import ToolContext, FunctionTool
24+
from google.apps import chat_v1
25+
from google.oauth2.credentials import Credentials
26+
27+
MODEL = "gemini-2.5-flash"
28+
29+
# Gemini Enterprise authentication injects a bearer token into the ToolContext state.
30+
# The key pattern is "GE_AUTH_NAME_<random_digits>".
31+
# We dynamically parse this token to authenticate our MCP and API calls.
32+
GE_AUTH_NAME = "enterprise-ai"
33+
34+
VERTEXAI_SEARCH_TIMEOUT = 15.0
35+
36+
def get_project_id():
37+
"""Fetches the consumer project ID from the environment natively."""
38+
_, project = google.auth.default()
39+
if project:
40+
return project
41+
raise Exception(f"Failed to resolve GCP Project ID from environment.")
42+
43+
def find_serving_config_path():
44+
"""Dynamically finds the default serving config in the engine."""
45+
project_id = get_project_id()
46+
engines = discoveryengine_v1.EngineServiceClient().list_engines(
47+
parent=f"projects/{project_id}/locations/global/collections/default_collection"
48+
)
49+
for engine in engines:
50+
# engine.name natively contains the numeric Project Number
51+
return f"{engine.name}/servingConfigs/default_serving_config"
52+
raise Exception(f"No Discovery Engines found in project {project_id}")
53+
54+
def _get_access_token_from_context(tool_context: ToolContext) -> str:
55+
"""Helper method to dynamically parse the intercepted bearer token from the context state."""
56+
escaped_name = re.escape(GE_AUTH_NAME)
57+
pattern = re.compile(fr"^{escaped_name}_\d+$")
58+
# Handle ADK varying state object types (Raw Dict vs ADK State)
59+
state_dict = tool_context.state.to_dict() if hasattr(tool_context.state, 'to_dict') else tool_context.state
60+
matching_keys = [k for k in state_dict.keys() if pattern.match(k)]
61+
if matching_keys:
62+
return state_dict.get(matching_keys[0])
63+
raise Exception(f"No bearer token found in ToolContext state matching pattern {pattern.pattern}")
64+
65+
def auth_header_provider(tool_context: ToolContext) -> dict[str, str]:
66+
token = _get_access_token_from_context(tool_context)
67+
return {"Authorization": f"Bearer {token}"}
68+
69+
def send_direct_message(email: str, message: str, tool_context: ToolContext) -> dict:
70+
"""Sends a Google Chat Direct Message (DM) to a specific user by email address."""
71+
chat_client = chat_v1.ChatServiceClient(
72+
credentials=Credentials(token=_get_access_token_from_context(tool_context))
73+
)
74+
75+
# 1. Setup the DM space or find existing one
76+
person = chat_v1.User(
77+
name=f"users/{email}",
78+
type_=chat_v1.User.Type.HUMAN
79+
)
80+
membership = chat_v1.Membership(member=person)
81+
space_req = chat_v1.Space(space_type=chat_v1.Space.SpaceType.DIRECT_MESSAGE)
82+
setup_request = chat_v1.SetUpSpaceRequest(
83+
space=space_req,
84+
memberships=[membership]
85+
)
86+
space_response = chat_client.set_up_space(request=setup_request)
87+
space_name = space_response.name
88+
89+
# 2. Send the message
90+
msg = chat_v1.Message(text=message)
91+
message_request = chat_v1.CreateMessageRequest(
92+
parent=space_name,
93+
message=msg
94+
)
95+
message_response = chat_client.create_message(request=message_request)
96+
97+
return {"status": "success", "message_id": message_response.name, "space": space_name}
98+
99+
vertexai_mcp = McpToolset(
100+
connection_params=StreamableHTTPConnectionParams(
101+
url="https://discoveryengine.googleapis.com/mcp",
102+
timeout=VERTEXAI_SEARCH_TIMEOUT,
103+
sse_read_timeout=VERTEXAI_SEARCH_TIMEOUT
104+
),
105+
tool_filter=['search'],
106+
# The auth_header_provider dynamically injects the bearer token from the ToolContext
107+
# into the MCP call for authentication.
108+
header_provider=auth_header_provider
109+
)
110+
111+
# Answer nicely the following user queries:
112+
# - Please find my meetings for today, I need their titles and links
113+
# - What is the latest Drive file I created?
114+
# - What is the latest Gmail message I received?
115+
# - Please send the following message to someone@example.com: Hello, this is a test message.
116+
117+
root_agent = LlmAgent(
118+
model=MODEL,
119+
name='enterprise_ai',
120+
instruction=f"""
121+
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.
122+
If the user asks you to send a message to someone, use the send_direct_message tool to send the message.
123+
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.
124+
The Vertex AI MCP search tool accesses the user's data through datastores including Google Drive, Google Calendar, and Gmail.
125+
Only use the Vertex AI MCP search tool with servingConfig and query parameters, do not use any other parameters.
126+
Always use the servingConfig {find_serving_config_path()} while using the Vertex AI MCP search tool.
127+
""",
128+
tools=[vertexai_mcp, FunctionTool(send_direct_message)]
129+
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
google-adk (>=1.25.1,<2.0.0)
16+
google-cloud-aiplatform[adk,agent_engines] (>=1.126.1,<2.0.0)
17+
google-genai (>=1.9.0,<2.0.0)
18+
pydantic (>=2.10.6,<3.0.0)
19+
absl-py (>=2.2.1,<3.0.0)
20+
google-cloud-discoveryengine (>=0.13.12,<0.14.0)
21+
google-apps-chat (>=0.6.0,<0.7.0)

0 commit comments

Comments
 (0)