Skip to content

Commit 2074889

Browse files
google-genai-botcopybara-github
authored andcommitted
feat: Support loading agents from Visual Builder with BigQuery-powered logging
PiperOrigin-RevId: 896612027
1 parent 9e73ab8 commit 2074889

8 files changed

Lines changed: 509 additions & 15 deletions

File tree

src/google/adk/agents/config_agent_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def from_config(config_path: str) -> BaseAgent:
3636
"""Build agent from a configfile path.
3737
3838
Args:
39-
config: the path to a YAML config file.
39+
config_path: the path to a YAML config file.
4040
4141
Returns:
4242
The created agent instance.

src/google/adk/cli/adk_web_server.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
from typing_extensions import deprecated
5656
from typing_extensions import override
5757
from watchdog.observers import Observer
58+
import yaml
5859

5960
from . import agent_graph
6061
from ..agents.base_agent import BaseAgent
@@ -89,6 +90,7 @@
8990
from ..events.event import Event
9091
from ..memory.base_memory_service import BaseMemoryService
9192
from ..plugins.base_plugin import BasePlugin
93+
from ..plugins.bigquery_agent_analytics_plugin import BigQueryAgentAnalyticsPlugin
9294
from ..runners import Runner
9395
from ..sessions.base_session_service import BaseSessionService
9496
from ..sessions.session import Session
@@ -697,17 +699,55 @@ async def get_runner_async(self, app_name: str) -> Runner:
697699
# Instantiate extra plugins if configured
698700
extra_plugins_instances = self._instantiate_extra_plugins()
699701

702+
plugins_yaml_path = os.path.join(self.agents_dir, app_name, "plugins.yaml")
703+
bq_analytics_config = None
704+
if os.path.exists(plugins_yaml_path):
705+
with open(plugins_yaml_path, "r", encoding="utf-8") as f:
706+
plugins_config = yaml.safe_load(f)
707+
if plugins_config and isinstance(plugins_config, dict):
708+
bq_analytics_config = plugins_config.get("bigquery_agent_analytics")
709+
710+
# Determine if the agent was loaded from YAML based on the agent loader info
711+
is_visual_builder = False
712+
detailed_agents = self.agent_loader.list_agents_detailed()
713+
for agent_info in detailed_agents:
714+
if agent_info.get("name") == app_name:
715+
if agent_info.get("language") == "yaml":
716+
is_visual_builder = True
717+
break
718+
700719
if isinstance(agent_or_app, BaseAgent):
720+
plugins = extra_plugins_instances
721+
722+
# Handle BigQuery Analytics Plugin injection
723+
if bq_analytics_config and all([
724+
bq_analytics_config.get("project_id"),
725+
bq_analytics_config.get("dataset_id"),
726+
bq_analytics_config.get("dataset_location"),
727+
]):
728+
plugins.append(
729+
BigQueryAgentAnalyticsPlugin(
730+
project_id=bq_analytics_config.get("project_id"),
731+
dataset_id=bq_analytics_config.get("dataset_id"),
732+
table_id=bq_analytics_config.get("table_id"),
733+
location=bq_analytics_config.get("dataset_location"),
734+
)
735+
)
736+
701737
agentic_app = App(
702738
name=app_name,
703739
root_agent=agent_or_app,
704-
plugins=extra_plugins_instances,
740+
plugins=plugins,
705741
)
706742
else:
707743
# Combine existing plugins with extra plugins
708744
agent_or_app.plugins = agent_or_app.plugins + extra_plugins_instances
709745
agentic_app = agent_or_app
710746

747+
# If the root agent was loaded from YAML, we treat it as being from Visual Builder
748+
if is_visual_builder:
749+
object.__setattr__(agentic_app, "_is_visual_builder_app", True)
750+
711751
runner = self._create_runner(agentic_app)
712752
self.runner_dict[app_name] = runner
713753
return runner
@@ -1840,9 +1880,20 @@ async def patch_memory(
18401880
raise HTTPException(status_code=404, detail="Session not found")
18411881
await self.memory_service.add_session_to_memory(session)
18421882

1883+
def _set_telemetry_context_if_needed(runner: Runner):
1884+
"""Helper to set contextvars for the current request task."""
1885+
app = getattr(runner, "app", None)
1886+
from ..utils._telemetry_context import _is_visual_builder
1887+
1888+
if app and getattr(app, "_is_visual_builder_app", False):
1889+
_is_visual_builder.set(True)
1890+
else:
1891+
_is_visual_builder.set(False)
1892+
18431893
@app.post("/run", response_model_exclude_none=True)
18441894
async def run_agent(req: RunAgentRequest) -> list[Event]:
18451895
runner = await self.get_runner_async(req.app_name)
1896+
_set_telemetry_context_if_needed(runner)
18461897
try:
18471898
async with Aclosing(
18481899
runner.run_async(
@@ -1864,6 +1915,7 @@ async def run_agent(req: RunAgentRequest) -> list[Event]:
18641915
async def run_agent_sse(req: RunAgentRequest) -> StreamingResponse:
18651916
stream_mode = StreamingMode.SSE if req.streaming else StreamingMode.NONE
18661917
runner = await self.get_runner_async(req.app_name)
1918+
_set_telemetry_context_if_needed(runner)
18671919

18681920
# Validate session existence before starting the stream.
18691921
# We check directly here instead of eagerly advancing the
@@ -2039,6 +2091,8 @@ async def run_agent_live(
20392091
return
20402092

20412093
await websocket.accept()
2094+
runner_for_context = await self.get_runner_async(app_name)
2095+
_set_telemetry_context_if_needed(runner_for_context)
20422096

20432097
session = await self.session_service.get_session(
20442098
app_name=app_name, user_id=user_id, session_id=session_id

src/google/adk/plugins/bigquery_agent_analytics_plugin.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
from ..models.llm_response import LlmResponse
6868
from ..tools.base_tool import BaseTool
6969
from ..tools.tool_context import ToolContext
70+
from ..utils._telemetry_context import _is_visual_builder
7071
from ..version import __version__
7172
from .base_plugin import BasePlugin
7273

@@ -922,6 +923,9 @@ def __init__(
922923
self.flush_interval = flush_interval
923924
self.retry_config = retry_config
924925
self.shutdown_timeout = shutdown_timeout
926+
927+
self._visual_builder = _is_visual_builder.get()
928+
925929
self._queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue(
926930
maxsize=queue_max_size
927931
)
@@ -1092,9 +1096,15 @@ async def _write_rows_with_retry(self, rows: list[dict[str, Any]]) -> None:
10921096
serialized_schema = self.arrow_schema.serialize().to_pybytes()
10931097
serialized_batch = arrow_batch.serialize().to_pybytes()
10941098

1099+
trace_id_prefix = (
1100+
"google-adk-bq-logger-visual-builder"
1101+
if self._visual_builder
1102+
else "google-adk-bq-logger"
1103+
)
1104+
10951105
req = bq_storage_types.AppendRowsRequest(
10961106
write_stream=self.write_stream,
1097-
trace_id=f"google-adk-bq-logger/{__version__}",
1107+
trace_id=f"{trace_id_prefix}/{__version__}",
10981108
)
10991109
req.arrow_rows.writer_schema.serialized_schema = serialized_schema
11001110
req.arrow_rows.rows.serialized_record_batch = serialized_batch
@@ -1900,6 +1910,8 @@ def __init__(
19001910
self.table_id = table_id or self.config.table_id
19011911
self.location = location
19021912

1913+
self._visual_builder = _is_visual_builder.get()
1914+
19031915
self._started = False
19041916
self._startup_error: Optional[Exception] = None
19051917
self._is_shutting_down = False
@@ -2030,9 +2042,12 @@ def get_credentials():
20302042
if quota_project_id
20312043
else None
20322044
)
2033-
client_info = gapic_client_info.ClientInfo(
2034-
user_agent=f"google-adk-bq-logger/{__version__}"
2035-
)
2045+
2046+
user_agents = [f"google-adk-bq-logger/{__version__}"]
2047+
if self._visual_builder:
2048+
user_agents.append(f"google-adk-visual-builder/{__version__}")
2049+
2050+
client_info = gapic_client_info.ClientInfo(user_agent=" ".join(user_agents))
20362051

20372052
write_client = BigQueryWriteAsyncClient(
20382053
credentials=creds,

src/google/adk/tools/bigquery/client.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from __future__ import annotations
1616

17+
import os
1718
from typing import List
1819
from typing import Optional
1920
from typing import Union
@@ -25,12 +26,16 @@
2526
from google.cloud import dataplex_v1
2627

2728
from ... import version
29+
from ...utils._telemetry_context import _is_visual_builder
2830

2931
USER_AGENT_BASE = f"google-adk/{version.__version__}"
3032
BQ_USER_AGENT = f"adk-bigquery-tool {USER_AGENT_BASE}"
3133
DP_USER_AGENT = f"adk-dataplex-tool {USER_AGENT_BASE}"
3234
USER_AGENT = BQ_USER_AGENT
3335

36+
# Internal identifier for Visual Builder usage tracking.
37+
_VISUAL_BUILDER_UA = "google-adk-visual-builder"
38+
3439

3540
def get_bigquery_client(
3641
*,
@@ -52,6 +57,10 @@ def get_bigquery_client(
5257
"""
5358

5459
user_agents = [BQ_USER_AGENT]
60+
61+
if _is_visual_builder.get():
62+
user_agents.append(_VISUAL_BUILDER_UA)
63+
5564
if user_agent:
5665
if isinstance(user_agent, str):
5766
user_agents.append(user_agent)
@@ -88,6 +97,10 @@ def get_dataplex_catalog_client(
8897
"""
8998

9099
user_agents = [DP_USER_AGENT]
100+
101+
if _is_visual_builder.get():
102+
user_agents.append(_VISUAL_BUILDER_UA)
103+
91104
if user_agent:
92105
if isinstance(user_agent, str):
93106
user_agents.append(user_agent)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
"""Context variables for internal telemetry use."""
16+
17+
from __future__ import annotations
18+
19+
import contextvars
20+
21+
# Internal context variable for Visual Builder usage tracking.
22+
# True if the current execution is within a Visual Builder context.
23+
_is_visual_builder: contextvars.ContextVar[bool] = contextvars.ContextVar(
24+
"_is_visual_builder", default=False
25+
)

0 commit comments

Comments
 (0)