Skip to content

Commit f020f1b

Browse files
authored
Merge branch 'main' into fix/orphaned-tool-calls-crash-loop
2 parents 5eb908f + 909a8c2 commit f020f1b

5 files changed

Lines changed: 971 additions & 0 deletions

File tree

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# GCP Auth Sample
2+
3+
Demonstrates the use of Agent Identity auth manager with an agent that queries
4+
Spotify and Google Maps using auth providers.
5+
6+
Use `adk web` to run API key and 2-legged oauth flows, while use the included
7+
custom agent web client to run 3-legged oauth flows.
8+
9+
## Setup
10+
11+
### 1. Activate environment
12+
13+
```bash
14+
cd adk-python
15+
python3 -m venv .venv
16+
source .venv/bin/activate
17+
```
18+
19+
### 2. Install dependencies
20+
21+
```bash
22+
pip install "google-adk[agent-identity]"
23+
```
24+
25+
### 3. Authenticate your environment
26+
27+
```bash
28+
gcloud auth application-default login
29+
export GOOGLE_CLOUD_PROJECT="YOUR_GOOGLE_CLOUD_PROJECT"
30+
gcloud auth application-default set-quota-project $GOOGLE_CLOUD_PROJECT
31+
```
32+
33+
### 4. Create auth providers
34+
35+
Refer to the [public documentation](https://cloud.google.com/iam/docs/manage-auth-providers) to create the following Agent Identity auth providers.
36+
37+
> **Note:**
38+
> The identity running the agent (via Application Default Credentials) must have
39+
> the necessary [permissions](https://docs.cloud.google.com/iam/docs/roles-permissions/iamconnectors#iamconnectors.user)
40+
> to retrieve credentials from these connectors. Ensure your account has the
41+
> necessary role to access these resources.
42+
43+
```bash
44+
export GOOGLE_CLOUD_LOCATION="YOUR_GOOGLE_CLOUD_LOCATION"
45+
export MAPS_API_AUTH_PROVIDER_ID="YOUR_MAPS_API_AUTH_PROVIDER_ID"
46+
export SPOTIFY_2LO_AUTH_PROVIDER_ID="YOUR_SPOTIFY_2LO_AUTH_PROVIDER_ID"
47+
export SPOTIFY_3LO_AUTH_PROVIDER_ID="YOUR_SPOTIFY_3LO_AUTH_PROVIDER_ID"
48+
49+
gcloud alpha agent-identity connectors create $MAPS_API_AUTH_PROVIDER_ID \
50+
--project=$GOOGLE_CLOUD_PROJECT \
51+
--location=$GOOGLE_CLOUD_LOCATION \
52+
--api-key=YOUR_API_KEY
53+
54+
gcloud alpha agent-identity connectors create $SPOTIFY_2LO_AUTH_PROVIDER_ID \
55+
--project=$GOOGLE_CLOUD_PROJECT \
56+
--location=$GOOGLE_CLOUD_LOCATION \
57+
--two-legged-oauth-client-id=OAUTH_CLIENT_ID \
58+
--two-legged-oauth-client-secret=OAUTH_CLIENT_SECRET \
59+
--two-legged-oauth-token-endpoint=OAUTH_TOKEN_ENDPOINT
60+
61+
gcloud alpha agent-identity connectors create $SPOTIFY_3LO_AUTH_PROVIDER_ID \
62+
--project=$GOOGLE_CLOUD_PROJECT \
63+
--location=$GOOGLE_CLOUD_LOCATION \
64+
--three-legged-oauth-client-id=OAUTH_CLIENT_ID \
65+
--three-legged-oauth-client-secret=OAUTH_CLIENT_SECRET \
66+
--three-legged-oauth-authorization-url=AUTHORIZATION_URL \
67+
--three-legged-oauth-token-url=TOKEN_URL \
68+
--allowed-scopes=ALLOWED_SCOPES
69+
```
70+
71+
### 5. Test API key and 2LO auth provider using ADK web client
72+
73+
```bash
74+
adk web contributing/samples
75+
```
76+
77+
- On the ADK web UI, select the agent named `gcp_auth` from the dropdown.
78+
- Sample queries to try:
79+
- API key (Google Maps tool): "What is the current weather in New York?"
80+
- 2LO key (Spotify tool): "Tell me about the song: Waving Flag"
81+
82+
### 6. Test 3LO auth provider using custom web client
83+
84+
> **Note:** If the agent backend is running on a different port or host other
85+
> than `localhost:8000`, please set the `AGENT_BACKEND_URL` environment variable
86+
> before starting the client (e.g.,
87+
> `export AGENT_BACKEND_URL="http://localhost:9000"`).
88+
89+
- In a separate shell, activate environment
90+
91+
```bash
92+
cd adk-python
93+
python3 -m venv .venv
94+
source .venv/bin/activate
95+
```
96+
97+
- Navigate to the client directory and install dependencies
98+
99+
```bash
100+
cd contributing/samples/gcp_auth/client
101+
pip install -r requirements.txt
102+
```
103+
104+
- Start the client application
105+
106+
```bash
107+
uvicorn main:app --port 8080 --reload
108+
```
109+
110+
- Open `http://localhost:8080`. (**Note:** You must use `localhost` and not `127.0.0.1`, as the OAuth redirect URL specifically requires it.)
111+
- On the login screen, enter an arbitrary user ID (e.g. test_user123).
112+
- Sample queries to try:
113+
- 3LO key (Spotify tool): "What are my private Spotify playlists?"
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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 __future__ import annotations
16+
17+
import os
18+
19+
from google.adk.agents import Agent
20+
from google.adk.apps import App
21+
from google.adk.auth.auth_credential import AuthCredential
22+
from google.adk.auth.auth_tool import AuthConfig
23+
from google.adk.auth.credential_manager import CredentialManager
24+
from google.adk.integrations.agent_identity import GcpAuthProvider
25+
from google.adk.integrations.agent_identity import GcpAuthProviderScheme
26+
from google.adk.tools.authenticated_function_tool import AuthenticatedFunctionTool
27+
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
28+
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
29+
import httpx
30+
31+
PROJECT_ID = os.environ.get("GOOGLE_CLOUD_PROJECT")
32+
LOCATION = os.environ.get("GOOGLE_CLOUD_LOCATION")
33+
MAPS_API_AUTH_PROVIDER_ID = os.environ.get("MAPS_API_AUTH_PROVIDER_ID")
34+
SPOTIFY_2LO_AUTH_PROVIDER_ID = os.environ.get("SPOTIFY_2LO_AUTH_PROVIDER_ID")
35+
SPOTIFY_3LO_AUTH_PROVIDER_ID = os.environ.get("SPOTIFY_3LO_AUTH_PROVIDER_ID")
36+
37+
MAPS_API_AUTH_PROVIDER = f"projects/{PROJECT_ID}/locations/{LOCATION}/connectors/{MAPS_API_AUTH_PROVIDER_ID}"
38+
SPOTIFY_2LO_AUTH_PROVIDER = f"projects/{PROJECT_ID}/locations/{LOCATION}/connectors/{SPOTIFY_2LO_AUTH_PROVIDER_ID}"
39+
SPOTIFY_3LO_AUTH_PROVIDER = f"projects/{PROJECT_ID}/locations/{LOCATION}/connectors/{SPOTIFY_3LO_AUTH_PROVIDER_ID}"
40+
41+
MAPS_MCP_ENDPOINT = "https://mapstools.googleapis.com/mcp"
42+
CONTINUE_URI = "http://localhost:8080/commit"
43+
MODEL = "gemini-2.5-flash"
44+
45+
46+
async def spotify_search_track(
47+
credential: AuthCredential, query: str
48+
) -> str | list:
49+
"""Searches for a track on Spotify and returns its details."""
50+
headers = {}
51+
if http := credential.http:
52+
if http.scheme and http.credentials and (token := http.credentials.token):
53+
headers["Authorization"] = f"{http.scheme.title()} {token}"
54+
if http.additional_headers:
55+
headers.update(http.additional_headers)
56+
57+
if not headers:
58+
return "Error: No authentication token available."
59+
60+
async with httpx.AsyncClient() as client:
61+
response = await client.get(
62+
"https://api.spotify.com/v1/search",
63+
headers=headers,
64+
params={"q": query, "type": "track", "limit": 1},
65+
)
66+
67+
if response.status_code != 200:
68+
return f"Error from Spotify API: {response.status_code} - {response.text}"
69+
70+
data = response.json()
71+
items = data.get("tracks", {}).get("items", [])
72+
73+
if not items:
74+
return f"No track found for query '{query}'."
75+
76+
return items
77+
78+
79+
async def spotify_get_playlists(credential: AuthCredential) -> str | list:
80+
"""Fetches the current user's private playlists on Spotify."""
81+
headers = {}
82+
if http := credential.http:
83+
if http.scheme and http.credentials and (token := http.credentials.token):
84+
headers["Authorization"] = f"{http.scheme.title()} {token}"
85+
if http.additional_headers:
86+
headers.update(http.additional_headers)
87+
88+
if not headers:
89+
return "Error: No authentication token available."
90+
91+
async with httpx.AsyncClient() as client:
92+
response = await client.get(
93+
"https://api.spotify.com/v1/me/playlists",
94+
headers=headers,
95+
params={"limit": 10},
96+
)
97+
98+
if response.status_code != 200:
99+
return f"Error from Spotify API: {response.status_code} - {response.text}"
100+
101+
data = response.json()
102+
items = data.get("items", [])
103+
104+
if not items:
105+
return "No playlists found for the current user."
106+
107+
# Extract useful information
108+
return [
109+
{
110+
"name": item.get("name"),
111+
"public": item.get("public"),
112+
"total_tracks": item.get("tracks", {}).get("total"),
113+
}
114+
for item in items
115+
if item
116+
]
117+
118+
119+
spotify_auth_config_2lo = AuthConfig(
120+
auth_scheme=GcpAuthProviderScheme(name=SPOTIFY_2LO_AUTH_PROVIDER)
121+
)
122+
spotify_search_track_tool = AuthenticatedFunctionTool(
123+
func=spotify_search_track,
124+
auth_config=spotify_auth_config_2lo,
125+
)
126+
127+
spotify_auth_config_3lo = AuthConfig(
128+
auth_scheme=GcpAuthProviderScheme(
129+
name=SPOTIFY_3LO_AUTH_PROVIDER,
130+
scopes=["playlist-read-private"],
131+
continue_uri=CONTINUE_URI,
132+
)
133+
)
134+
spotify_get_playlist_tool = AuthenticatedFunctionTool(
135+
func=spotify_get_playlists,
136+
auth_config=spotify_auth_config_3lo,
137+
)
138+
139+
maps_tools = McpToolset(
140+
connection_params=StreamableHTTPConnectionParams(url=MAPS_MCP_ENDPOINT),
141+
auth_scheme=GcpAuthProviderScheme(name=MAPS_API_AUTH_PROVIDER),
142+
errlog=None, # Required for agent freezing (pickling)
143+
)
144+
145+
CredentialManager.register_auth_provider(GcpAuthProvider())
146+
147+
root_agent = Agent(
148+
name="gcp_auth_agent",
149+
model=MODEL,
150+
instruction=(
151+
"You are a Spotify and Google Maps assistant. Use your tools to "
152+
"search for track details, fetch the user's private playlists, "
153+
"and look up locations. Keep responses concise, friendly, and "
154+
"emoji-filled!"
155+
),
156+
tools=[
157+
spotify_search_track_tool,
158+
spotify_get_playlist_tool,
159+
maps_tools,
160+
],
161+
)
162+
163+
app = App(
164+
name="gcp_auth",
165+
root_agent=root_agent,
166+
)

0 commit comments

Comments
 (0)