Skip to content

Commit 5278d3e

Browse files
google-genai-botcopybara-github
authored andcommitted
feat: add ADK trigger endpoints to ADK Web Server
PiperOrigin-RevId: 872815602
1 parent 6ee0362 commit 5278d3e

22 files changed

+2460
-53
lines changed

src/google/adk/cli/adk_web_server.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ def __init__(
651651
logo_image_url: Optional[str] = None,
652652
url_prefix: Optional[str] = None,
653653
auto_create_session: bool = False,
654+
trigger_sources: Optional[list[str]] = None,
654655
):
655656
self.agent_loader = agent_loader
656657
self.session_service = session_service
@@ -669,6 +670,7 @@ def __init__(
669670
self.runner_dict = {}
670671
self.url_prefix = url_prefix
671672
self.auto_create_session = auto_create_session
673+
self.trigger_sources = trigger_sources
672674

673675
async def get_runner_async(self, app_name: str) -> Runner:
674676
"""Returns the cached runner for the given app."""
@@ -2058,6 +2060,12 @@ async def process_messages():
20582060
for task in pending:
20592061
task.cancel()
20602062

2063+
# Register /trigger/* endpoints.
2064+
from .trigger_routes import TriggerRouter
2065+
2066+
trigger_router = TriggerRouter(self, trigger_sources=self.trigger_sources)
2067+
trigger_router.register(app)
2068+
20612069
if web_assets_dir:
20622070
import mimetypes
20632071

src/google/adk/cli/cli_deploy.py

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def _ensure_agent_engine_dependency(requirements_txt_path: str) -> None:
9999
100100
EXPOSE {port}
101101
102-
CMD adk {command} --port={port} {host_option} {service_option} {trace_to_cloud_option} {otel_to_cloud_option} {allow_origins_option} {a2a_option} "/app/agents"
102+
CMD {cmd_exec}
103103
"""
104104

105105
_AGENT_ENGINE_APP_TEMPLATE: Final[str] = """
@@ -591,8 +591,8 @@ def _get_service_option_by_adk_version(
591591
artifact_uri: Optional[str],
592592
memory_uri: Optional[str],
593593
use_local_storage: Optional[bool] = None,
594-
) -> str:
595-
"""Returns service option string based on adk_version."""
594+
) -> list[str]:
595+
"""Returns service options based on adk_version."""
596596
parsed_version = parse(adk_version)
597597
options: list[str] = []
598598

@@ -621,7 +621,7 @@ def _get_service_option_by_adk_version(
621621
else '--no_use_local_storage'
622622
))
623623

624-
return ' '.join(options)
624+
return options
625625

626626

627627
def to_cloud_run(
@@ -645,6 +645,7 @@ def to_cloud_run(
645645
memory_service_uri: Optional[str] = None,
646646
use_local_storage: bool = False,
647647
a2a: bool = False,
648+
trigger_sources: Optional[str] = None,
648649
extra_gcloud_args: Optional[tuple[str, ...]] = None,
649650
):
650651
"""Deploys an agent to Google Cloud Run.
@@ -710,31 +711,45 @@ def to_cloud_run(
710711

711712
# create Dockerfile
712713
click.echo('Creating Dockerfile...')
713-
host_option = '--host=0.0.0.0' if adk_version > '0.5.0' else ''
714-
allow_origins_option = (
715-
f'--allow_origins={",".join(allow_origins)}' if allow_origins else ''
714+
cmd_args = [
715+
'adk',
716+
'web' if with_ui else 'api_server',
717+
f'--port={port}',
718+
]
719+
if adk_version > '0.5.0':
720+
cmd_args.append('--host=0.0.0.0')
721+
722+
cmd_args.extend(
723+
_get_service_option_by_adk_version(
724+
adk_version,
725+
session_service_uri,
726+
artifact_service_uri,
727+
memory_service_uri,
728+
use_local_storage,
729+
)
716730
)
717-
a2a_option = '--a2a' if a2a else ''
731+
732+
if trace_to_cloud:
733+
cmd_args.append('--trace_to_cloud')
734+
if otel_to_cloud:
735+
cmd_args.append('--otel_to_cloud')
736+
if allow_origins:
737+
cmd_args.append(f'--allow_origins={",".join(allow_origins)}')
738+
if a2a:
739+
cmd_args.append('--a2a')
740+
if trigger_sources:
741+
cmd_args.append(f'--trigger_sources={trigger_sources}')
742+
743+
cmd_args.append('/app/agents')
744+
718745
dockerfile_content = _DOCKERFILE_TEMPLATE.format(
719746
gcp_project_id=project,
720747
gcp_region=region,
721748
app_name=app_name,
722749
port=port,
723-
command='web' if with_ui else 'api_server',
724750
install_agent_deps=install_agent_deps,
725-
service_option=_get_service_option_by_adk_version(
726-
adk_version,
727-
session_service_uri,
728-
artifact_service_uri,
729-
memory_service_uri,
730-
use_local_storage,
731-
),
732-
trace_to_cloud_option='--trace_to_cloud' if trace_to_cloud else '',
733-
otel_to_cloud_option='--otel_to_cloud' if otel_to_cloud else '',
734-
allow_origins_option=allow_origins_option,
735751
adk_version=adk_version,
736-
host_option=host_option,
737-
a2a_option=a2a_option,
752+
cmd_exec=json.dumps(cmd_args),
738753
)
739754
dockerfile_path = os.path.join(temp_folder, 'Dockerfile')
740755
os.makedirs(temp_folder, exist_ok=True)
@@ -1171,6 +1186,7 @@ def to_gke(
11711186
memory_service_uri: Optional[str] = None,
11721187
use_local_storage: bool = False,
11731188
a2a: bool = False,
1189+
trigger_sources: Optional[str] = None,
11741190
service_type: Literal[
11751191
'ClusterIP', 'NodePort', 'LoadBalancer'
11761192
] = 'ClusterIP',
@@ -1247,27 +1263,45 @@ def to_gke(
12471263
# create Dockerfile
12481264
click.secho('\nSTEP 2: Generating deployment files...', bold=True)
12491265
click.echo(' - Creating Dockerfile...')
1250-
host_option = '--host=0.0.0.0' if adk_version > '0.5.0' else ''
1266+
cmd_args = [
1267+
'adk',
1268+
'web' if with_ui else 'api_server',
1269+
f'--port={port}',
1270+
]
1271+
if adk_version > '0.5.0':
1272+
cmd_args.append('--host=0.0.0.0')
1273+
1274+
cmd_args.extend(
1275+
_get_service_option_by_adk_version(
1276+
adk_version,
1277+
session_service_uri,
1278+
artifact_service_uri,
1279+
memory_service_uri,
1280+
use_local_storage,
1281+
)
1282+
)
1283+
1284+
if trace_to_cloud:
1285+
cmd_args.append('--trace_to_cloud')
1286+
if otel_to_cloud:
1287+
cmd_args.append('--otel_to_cloud')
1288+
if allow_origins:
1289+
cmd_args.append(f'--allow_origins={",".join(allow_origins)}')
1290+
if a2a:
1291+
cmd_args.append('--a2a')
1292+
if trigger_sources:
1293+
cmd_args.append(f'--trigger_sources={trigger_sources}')
1294+
1295+
cmd_args.append('/app/agents')
1296+
12511297
dockerfile_content = _DOCKERFILE_TEMPLATE.format(
12521298
gcp_project_id=project,
12531299
gcp_region=region,
12541300
app_name=app_name,
12551301
port=port,
1256-
command='web' if with_ui else 'api_server',
12571302
install_agent_deps=install_agent_deps,
1258-
service_option=_get_service_option_by_adk_version(
1259-
adk_version,
1260-
session_service_uri,
1261-
artifact_service_uri,
1262-
memory_service_uri,
1263-
use_local_storage,
1264-
),
1265-
trace_to_cloud_option='--trace_to_cloud' if trace_to_cloud else '',
1266-
otel_to_cloud_option='--otel_to_cloud' if otel_to_cloud else '',
1267-
allow_origins_option=allow_origins_option,
12681303
adk_version=adk_version,
1269-
host_option=host_option,
1270-
a2a_option='--a2a' if a2a else '',
1304+
cmd_exec=json.dumps(cmd_args),
12711305
)
12721306
dockerfile_path = os.path.join(temp_folder, 'Dockerfile')
12731307
os.makedirs(temp_folder, exist_ok=True)

src/google/adk/cli/cli_tools_click.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,7 @@ def fast_api_common_options():
13291329
"""Decorator to add common fast api options to click commands."""
13301330

13311331
def decorator(func):
1332+
13321333
@click.option(
13331334
"--host",
13341335
type=str,
@@ -1433,6 +1434,17 @@ def decorator(func):
14331434
),
14341435
default=None,
14351436
)
1437+
# Parsed into list[str] by the wrapper below (server commands need a list).
1438+
@click.option(
1439+
"--trigger_sources",
1440+
type=str,
1441+
help=(
1442+
"Optional. Comma-separated list of trigger sources to enable"
1443+
" (e.g., 'pubsub,eventarc'). Registers /apps/{app_name}/trigger/*"
1444+
" endpoints for batch and event-driven agent invocations."
1445+
),
1446+
default=None,
1447+
)
14361448
@functools.wraps(func)
14371449
@click.pass_context
14381450
def wrapper(ctx, *args, **kwargs):
@@ -1444,6 +1456,21 @@ def wrapper(ctx, *args, **kwargs):
14441456
):
14451457
kwargs["log_level"] = "DEBUG"
14461458

1459+
# Parse comma-separated trigger_sources into a list and validate.
1460+
trigger_sources_raw = kwargs.get("trigger_sources")
1461+
if trigger_sources_raw is not None:
1462+
valid_sources = ["pubsub", "eventarc"]
1463+
parsed_sources = [
1464+
s.strip() for s in trigger_sources_raw.split(",") if s.strip()
1465+
]
1466+
invalid = [s for s in parsed_sources if s not in valid_sources]
1467+
if invalid:
1468+
raise click.BadParameter(
1469+
f"Invalid trigger source(s): {', '.join(invalid)}. "
1470+
f"Valid sources are: {', '.join(valid_sources)}"
1471+
)
1472+
kwargs["trigger_sources"] = parsed_sources
1473+
14471474
return func(*args, **kwargs)
14481475

14491476
return wrapper
@@ -1486,6 +1513,7 @@ def cli_web(
14861513
extra_plugins: Optional[list[str]] = None,
14871514
logo_text: Optional[str] = None,
14881515
logo_image_url: Optional[str] = None,
1516+
trigger_sources: Optional[list[str]] = None,
14891517
):
14901518
"""Starts a FastAPI server with Web UI for agents.
14911519
@@ -1542,6 +1570,7 @@ async def _lifespan(app: FastAPI):
15421570
extra_plugins=extra_plugins,
15431571
logo_text=logo_text,
15441572
logo_image_url=logo_image_url,
1573+
trigger_sources=trigger_sources,
15451574
)
15461575
config = uvicorn.Config(
15471576
app,
@@ -1597,6 +1626,7 @@ def cli_api_server(
15971626
reload_agents: bool = False,
15981627
extra_plugins: Optional[list[str]] = None,
15991628
auto_create_session: bool = False,
1629+
trigger_sources: Optional[list[str]] = None,
16001630
):
16011631
"""Starts a FastAPI server for agents.
16021632
@@ -1630,6 +1660,7 @@ def cli_api_server(
16301660
reload_agents=reload_agents,
16311661
extra_plugins=extra_plugins,
16321662
auto_create_session=auto_create_session,
1663+
trigger_sources=trigger_sources,
16331664
),
16341665
host=host,
16351666
port=port,
@@ -1763,6 +1794,17 @@ def cli_api_server(
17631794
default=False,
17641795
help="Optional. Whether to enable A2A endpoint.",
17651796
)
1797+
# Kept as raw str (not parsed to list) — interpolated directly into Dockerfile CMD.
1798+
@click.option(
1799+
"--trigger_sources",
1800+
type=str,
1801+
help=(
1802+
"Optional. Comma-separated list of trigger sources to enable"
1803+
" (e.g., 'pubsub,eventarc'). Registers /trigger/* endpoints"
1804+
" for batch and event-driven agent invocations."
1805+
),
1806+
default=None,
1807+
)
17661808
@click.option(
17671809
"--allow_origins",
17681810
help=(
@@ -1799,6 +1841,7 @@ def cli_deploy_cloud_run(
17991841
session_db_url: Optional[str] = None, # Deprecated
18001842
artifact_storage_uri: Optional[str] = None, # Deprecated
18011843
a2a: bool = False,
1844+
trigger_sources: Optional[str] = None,
18021845
):
18031846
"""Deploys an agent to Cloud Run.
18041847
@@ -1876,6 +1919,7 @@ def cli_deploy_cloud_run(
18761919
memory_service_uri=memory_service_uri,
18771920
use_local_storage=use_local_storage,
18781921
a2a=a2a,
1922+
trigger_sources=trigger_sources,
18791923
extra_gcloud_args=tuple(gcloud_args),
18801924
)
18811925
except Exception as e:
@@ -2272,6 +2316,17 @@ def cli_deploy_agent_engine(
22722316
" version in the dev environment)"
22732317
),
22742318
)
2319+
# Kept as raw str (not parsed to list) — interpolated directly into Dockerfile CMD.
2320+
@click.option(
2321+
"--trigger_sources",
2322+
type=str,
2323+
help=(
2324+
"Optional. Comma-separated list of trigger sources to enable"
2325+
" (e.g., 'pubsub,eventarc'). Registers /trigger/* endpoints"
2326+
" for batch and event-driven agent invocations."
2327+
),
2328+
default=None,
2329+
)
22752330
@adk_services_options(default_use_local_storage=False)
22762331
@click.argument(
22772332
"agent",
@@ -2298,6 +2353,7 @@ def cli_deploy_gke(
22982353
artifact_service_uri: Optional[str] = None,
22992354
memory_service_uri: Optional[str] = None,
23002355
use_local_storage: bool = False,
2356+
trigger_sources: Optional[str] = None,
23012357
):
23022358
"""Deploys an agent to GKE.
23032359
@@ -2329,6 +2385,7 @@ def cli_deploy_gke(
23292385
artifact_service_uri=artifact_service_uri,
23302386
memory_service_uri=memory_service_uri,
23312387
use_local_storage=use_local_storage,
2388+
trigger_sources=trigger_sources,
23322389
)
23332390
except Exception as e:
23342391
click.secho(f"Deploy failed: {e}", fg="red", err=True)

src/google/adk/cli/fast_api.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def get_fast_api_app(
9494
logo_text: Optional[str] = None,
9595
logo_image_url: Optional[str] = None,
9696
auto_create_session: bool = False,
97+
trigger_sources: Optional[list[str]] = None,
9798
) -> FastAPI:
9899
"""Constructs and returns a FastAPI application for serving ADK agents.
99100
@@ -135,8 +136,11 @@ def get_fast_api_app(
135136
extra_plugins: List of extra plugin names to load.
136137
logo_text: Text to display in the web UI logo area.
137138
logo_image_url: URL for an image to display in the web UI logo area.
138-
auto_create_session: Whether to automatically create a session when
139-
not found.
139+
auto_create_session: Whether to automatically create a session when not
140+
found.
141+
trigger_sources: List of trigger sources to enable (e.g. ["pubsub",
142+
"eventarc"]). When set, registers /trigger/* endpoints for batch and
143+
event-driven agent invocations. None disables all trigger endpoints.
140144
141145
Returns:
142146
The configured FastAPI application instance.
@@ -205,6 +209,7 @@ def get_fast_api_app(
205209
logo_image_url=logo_image_url,
206210
url_prefix=url_prefix,
207211
auto_create_session=auto_create_session,
212+
trigger_sources=trigger_sources,
208213
)
209214

210215
# Callbacks & other optional args for when constructing the FastAPI instance

0 commit comments

Comments
 (0)