Skip to content

Commit 2b04996

Browse files
wukathcopybara-github
authored andcommitted
feat: Add express mode onboarding support to adk deploy cli
Previously it was just in adk create, now add it to adk deploy in case users create an agent on their own and need to deploy with api key. Moved the shared logic into a utils/onboarding.py file Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 908191494
1 parent 2343973 commit 2b04996

5 files changed

Lines changed: 444 additions & 324 deletions

File tree

src/google/adk/cli/cli_create.py

Lines changed: 12 additions & 270 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@
1515
from __future__ import annotations
1616

1717
import os
18-
import subprocess
1918
from typing import Optional
20-
from typing import Tuple
2119

2220
import click
2321

2422
from ..apps.app import validate_app_name
25-
from .utils import gcp_utils
23+
from .utils import _onboarding
2624

2725
_INIT_PY_TEMPLATE = """\
2826
from . import agent
@@ -48,33 +46,11 @@
4846
"""
4947

5048

51-
_GOOGLE_API_MSG = """
52-
Don't have API Key? Create one in AI Studio: https://aistudio.google.com/apikey
53-
"""
54-
55-
_GOOGLE_CLOUD_SETUP_MSG = """
56-
You need an existing Google Cloud account and project, check out this link for details:
57-
https://google.github.io/adk-docs/get-started/quickstart/#gemini---google-cloud-vertex-ai
58-
"""
59-
6049
_OTHER_MODEL_MSG = """
6150
Please see below guide to configure other models:
6251
https://google.github.io/adk-docs/agents/models
6352
"""
6453

65-
_EXPRESS_TOS_MSG = """
66-
Google Cloud Express Mode Terms of Service: https://cloud.google.com/terms/google-cloud-express
67-
By using this application, you agree to the Google Cloud Express Mode terms of service and any
68-
applicable services and APIs: https://console.cloud.google.com/terms. You also agree to only use
69-
this application for your trade, business, craft, or profession.
70-
"""
71-
72-
_NOT_ELIGIBLE_MSG = """
73-
You are not eligible for Express Mode.
74-
Please follow these instructions to set up a full Google Cloud project:
75-
https://google.github.io/adk-docs/get-started/quickstart/#gemini---google-cloud-vertex-ai
76-
"""
77-
7854
_SUCCESS_MSG_CODE = """
7955
Agent created in {agent_folder}:
8056
- .env
@@ -96,98 +72,6 @@
9672
"""
9773

9874

99-
def _get_gcp_project_from_gcloud() -> str:
100-
"""Uses gcloud to get default project."""
101-
try:
102-
result = subprocess.run(
103-
["gcloud", "config", "get-value", "project"],
104-
capture_output=True,
105-
text=True,
106-
check=True,
107-
)
108-
return result.stdout.strip()
109-
except (subprocess.CalledProcessError, FileNotFoundError):
110-
return ""
111-
112-
113-
def _get_gcp_region_from_gcloud() -> str:
114-
"""Uses gcloud to get default region."""
115-
try:
116-
result = subprocess.run(
117-
["gcloud", "config", "get-value", "compute/region"],
118-
capture_output=True,
119-
text=True,
120-
check=True,
121-
)
122-
return result.stdout.strip()
123-
except (subprocess.CalledProcessError, FileNotFoundError):
124-
return ""
125-
126-
127-
def _prompt_str(
128-
prompt_prefix: str,
129-
*,
130-
prior_msg: Optional[str] = None,
131-
default_value: Optional[str] = None,
132-
) -> str:
133-
if prior_msg:
134-
click.secho(prior_msg, fg="green")
135-
while True:
136-
value: str = click.prompt(
137-
prompt_prefix, default=default_value or None, type=str
138-
)
139-
if value and value.strip():
140-
return value.strip()
141-
142-
143-
def _prompt_for_google_cloud(
144-
google_cloud_project: Optional[str],
145-
) -> str:
146-
"""Prompts user for Google Cloud project ID."""
147-
google_cloud_project = (
148-
google_cloud_project
149-
or os.environ.get("GOOGLE_CLOUD_PROJECT", None)
150-
or _get_gcp_project_from_gcloud()
151-
)
152-
153-
google_cloud_project = _prompt_str(
154-
"Enter Google Cloud project ID", default_value=google_cloud_project
155-
)
156-
157-
return google_cloud_project
158-
159-
160-
def _prompt_for_google_cloud_region(
161-
google_cloud_region: Optional[str],
162-
) -> str:
163-
"""Prompts user for Google Cloud region."""
164-
google_cloud_region = (
165-
google_cloud_region
166-
or os.environ.get("GOOGLE_CLOUD_LOCATION", None)
167-
or _get_gcp_region_from_gcloud()
168-
)
169-
170-
google_cloud_region = _prompt_str(
171-
"Enter Google Cloud region",
172-
default_value=google_cloud_region or "us-central1",
173-
)
174-
return google_cloud_region
175-
176-
177-
def _prompt_for_google_api_key(
178-
google_api_key: Optional[str],
179-
) -> str:
180-
"""Prompts user for Google API key."""
181-
google_api_key = google_api_key or os.environ.get("GOOGLE_API_KEY", None)
182-
183-
google_api_key = _prompt_str(
184-
"Enter Google API key",
185-
prior_msg=_GOOGLE_API_MSG,
186-
default_value=google_api_key,
187-
)
188-
return google_api_key
189-
190-
19175
def _generate_files(
19276
agent_folder: str,
19377
*,
@@ -256,155 +140,6 @@ def _prompt_for_model() -> str:
256140
return "<FILL_IN_MODEL>"
257141

258142

259-
def _prompt_to_choose_backend(
260-
google_api_key: Optional[str],
261-
google_cloud_project: Optional[str],
262-
google_cloud_region: Optional[str],
263-
) -> Tuple[Optional[str], Optional[str], Optional[str]]:
264-
"""Prompts user to choose backend.
265-
266-
Returns:
267-
A tuple of (google_api_key, google_cloud_project, google_cloud_region).
268-
"""
269-
backend_choice = click.prompt(
270-
"1. Google AI\n2. Vertex AI\n3. Login with Google\nChoose a backend",
271-
type=click.Choice(["1", "2", "3"]),
272-
)
273-
if backend_choice == "1":
274-
google_api_key = _prompt_for_google_api_key(google_api_key)
275-
elif backend_choice == "2":
276-
click.secho(_GOOGLE_CLOUD_SETUP_MSG, fg="green")
277-
google_cloud_project = _prompt_for_google_cloud(google_cloud_project)
278-
google_cloud_region = _prompt_for_google_cloud_region(google_cloud_region)
279-
elif backend_choice == "3":
280-
google_api_key, google_cloud_project, google_cloud_region = (
281-
_handle_login_with_google()
282-
)
283-
return google_api_key, google_cloud_project, google_cloud_region
284-
285-
286-
def _handle_login_with_google() -> (
287-
Tuple[Optional[str], Optional[str], Optional[str]]
288-
):
289-
"""Handles the "Login with Google" flow."""
290-
if not gcp_utils.check_adc():
291-
click.secho(
292-
"No Application Default Credentials found. "
293-
"Opening browser for login...",
294-
fg="yellow",
295-
)
296-
try:
297-
gcp_utils.login_adc()
298-
except RuntimeError as e:
299-
click.secho(str(e), fg="red")
300-
raise click.Abort()
301-
302-
# Check for existing Express project
303-
express_project = gcp_utils.retrieve_express_project()
304-
if express_project:
305-
api_key = express_project.get("api_key")
306-
project_id = express_project.get("project_id")
307-
region = express_project.get("region", "us-central1")
308-
if project_id:
309-
click.secho(f"Using existing Express project: {project_id}", fg="green")
310-
return api_key, project_id, region
311-
312-
# Check for existing full GCP projects
313-
projects = gcp_utils.list_gcp_projects(limit=20)
314-
if projects:
315-
click.secho("Recently created Google Cloud projects found:", fg="green")
316-
click.echo("0. Enter project ID manually")
317-
for i, (p_id, p_name) in enumerate(projects, 1):
318-
click.echo(f"{i}. {p_name} ({p_id})")
319-
320-
project_index = click.prompt(
321-
"Select a project",
322-
type=click.IntRange(0, len(projects)),
323-
)
324-
if project_index == 0:
325-
selected_project_id = _prompt_for_google_cloud(None)
326-
else:
327-
selected_project_id = projects[project_index - 1][0]
328-
region = _prompt_for_google_cloud_region(None)
329-
return None, selected_project_id, region
330-
331-
click.secho(
332-
"A Google Cloud project is required to continue. You can enter an"
333-
" existing project ID or create an Express Mode project. Learn more:"
334-
" https://cloud.google.com/resources/cloud-express-faqs",
335-
fg="green",
336-
)
337-
action = click.prompt(
338-
"1. Enter an existing Google Cloud project ID\n"
339-
"2. Create a new project (Express Mode)\n"
340-
"3. Abandon\n"
341-
"Choose an action",
342-
type=click.Choice(["1", "2", "3"]),
343-
)
344-
345-
if action == "3":
346-
raise click.Abort()
347-
348-
if action == "1":
349-
google_cloud_project = _prompt_for_google_cloud(None)
350-
google_cloud_region = _prompt_for_google_cloud_region(None)
351-
return None, google_cloud_project, google_cloud_region
352-
353-
elif action == "2":
354-
if gcp_utils.check_express_eligibility():
355-
click.secho(_EXPRESS_TOS_MSG, fg="yellow")
356-
if click.confirm("Do you accept the Terms of Service?", default=False):
357-
selected_region = click.prompt(
358-
"""\
359-
Choose a region for Express Mode:
360-
1. us-central1
361-
2. europe-west1
362-
3. asia-southeast1
363-
Choose region""",
364-
type=click.Choice(["1", "2", "3"]),
365-
default="1",
366-
)
367-
region_map = {
368-
"1": "us-central1",
369-
"2": "europe-west1",
370-
"3": "asia-southeast1",
371-
}
372-
region = region_map[selected_region]
373-
express_info = gcp_utils.sign_up_express(location=region)
374-
api_key = express_info.get("api_key")
375-
project_id = express_info.get("project_id")
376-
region = express_info.get("region", region)
377-
click.secho(
378-
f"Express Mode project created: {project_id}",
379-
fg="green",
380-
)
381-
current_proj = _get_gcp_project_from_gcloud()
382-
if current_proj and current_proj != project_id:
383-
click.secho(
384-
"Warning: Your default gcloud project is set to"
385-
f" '{current_proj}'. This might conflict with or override your"
386-
f" Express Mode project '{project_id}'. We recommend"
387-
" unsetting it.",
388-
fg="yellow",
389-
)
390-
if click.confirm("Run 'gcloud config unset project'?", default=True):
391-
try:
392-
subprocess.run(
393-
["gcloud", "config", "unset", "project"],
394-
check=True,
395-
capture_output=True,
396-
)
397-
click.secho("Unset default gcloud project.", fg="green")
398-
except Exception:
399-
click.secho(
400-
"Failed to unset project. Please do it manually.", fg="red"
401-
)
402-
return api_key, project_id, region
403-
404-
click.secho(_NOT_ELIGIBLE_MSG, fg="red")
405-
raise click.Abort()
406-
407-
408143
def _prompt_to_choose_type() -> str:
409144
"""Prompts user to choose type of agent to create."""
410145
type_choice = click.prompt(
@@ -464,11 +199,18 @@ def run_cmd(
464199

465200
if not google_api_key and not (google_cloud_project and google_cloud_region):
466201
if model.startswith("gemini"):
467-
google_api_key, google_cloud_project, google_cloud_region = (
468-
_prompt_to_choose_backend(
469-
google_api_key, google_cloud_project, google_cloud_region
470-
)
202+
auth_info = _onboarding.prompt_to_choose_backend(
203+
google_api_key, google_cloud_project, google_cloud_region
471204
)
205+
if isinstance(auth_info, _onboarding.GoogleAIAuth):
206+
google_api_key = auth_info.api_key
207+
elif isinstance(auth_info, _onboarding.VertexAIAuth):
208+
google_cloud_project = auth_info.project_id
209+
google_cloud_region = auth_info.region
210+
elif isinstance(auth_info, _onboarding.ExpressModeAuth):
211+
google_api_key = auth_info.api_key
212+
google_cloud_project = auth_info.project_id
213+
google_cloud_region = auth_info.region
472214

473215
if not type:
474216
type = _prompt_to_choose_type()

src/google/adk/cli/cli_deploy.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import click
3030
from packaging.version import parse
3131

32+
from .utils import _onboarding
33+
3234
_IS_WINDOWS = os.name == 'nt'
3335
_GCLOUD_CMD = 'gcloud.cmd' if _IS_WINDOWS else 'gcloud'
3436
_LOCAL_STORAGE_FLAG_MIN_VERSION: Final[str] = '1.21.0'
@@ -1108,10 +1110,22 @@ def to_agent_engine(
11081110
)
11091111
else:
11101112
click.echo(
1111-
'No project/region or api_key provided. '
1112-
'Please specify either project/region or api_key.'
1113+
'No project/region or api_key provided. Starting onboarding flow...'
11131114
)
1114-
return
1115+
auth_info = _onboarding.handle_login_with_google()
1116+
if isinstance(auth_info, _onboarding.VertexAIAuth):
1117+
click.echo('Initializing Vertex AI...')
1118+
client = vertexai.Client(
1119+
project=auth_info.project_id,
1120+
location=auth_info.region,
1121+
http_options={'headers': get_tracking_headers()},
1122+
)
1123+
elif isinstance(auth_info, _onboarding.ExpressModeAuth):
1124+
click.echo('Initializing Vertex AI in Express Mode with API key...')
1125+
client = vertexai.Client(
1126+
api_key=auth_info.api_key,
1127+
http_options={'headers': get_tracking_headers()},
1128+
)
11151129
click.echo('Vertex AI initialized.')
11161130

11171131
is_config_agent = False

0 commit comments

Comments
 (0)