Skip to content

Commit b0d4465

Browse files
authored
Merge branch 'main' into main
2 parents 40e93e0 + 53b67ce commit b0d4465

15 files changed

Lines changed: 592 additions & 39 deletions

src/google/adk/cli/cli_deploy.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,7 @@ def to_agent_engine(
689689
adk_app: str,
690690
staging_bucket: Optional[str] = None,
691691
trace_to_cloud: Optional[bool] = None,
692+
otel_to_cloud: Optional[bool] = None,
692693
api_key: Optional[str] = None,
693694
adk_app_object: Optional[str] = None,
694695
agent_engine_id: Optional[str] = None,
@@ -733,6 +734,8 @@ def to_agent_engine(
733734
staging_bucket (str): Deprecated. This argument is no longer required or
734735
used.
735736
trace_to_cloud (bool): Whether to enable Cloud Trace.
737+
otel_to_cloud (bool): Whether to enable exporting OpenTelemetry signals
738+
to Google Cloud.
736739
api_key (str): Optional. The API key to use for Express Mode.
737740
If not provided, the API key from the GOOGLE_API_KEY environment variable
738741
will be used. It will only be used if GOOGLE_GENAI_USE_VERTEXAI is true.
@@ -910,6 +913,14 @@ def to_agent_engine(
910913
if 'GOOGLE_API_KEY' in env_vars:
911914
api_key = env_vars['GOOGLE_API_KEY']
912915
click.echo(f'api_key set by GOOGLE_API_KEY in {env_file}')
916+
if otel_to_cloud:
917+
if 'GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY' in env_vars:
918+
click.secho(
919+
'Ignoring GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY in .env'
920+
' as `--otel_to_cloud` was explicitly passed and takes precedence',
921+
fg='yellow',
922+
)
923+
env_vars['GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY'] = 'true'
913924
if env_vars:
914925
if 'env_vars' in agent_config:
915926
click.echo(

src/google/adk/cli/cli_tools_click.py

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,44 @@
5050
)
5151

5252

53-
def _apply_feature_overrides(enable_features: tuple[str, ...]) -> None:
53+
def _apply_feature_overrides(
54+
*,
55+
enable_features: tuple[str, ...] = (),
56+
disable_features: tuple[str, ...] = (),
57+
) -> None:
5458
"""Apply feature overrides from CLI flags.
5559
5660
Args:
5761
enable_features: Tuple of feature names to enable.
62+
disable_features: Tuple of feature names to disable.
5863
"""
64+
feature_overrides: dict[str, bool] = {}
65+
5966
for features_str in enable_features:
6067
for feature_name_str in features_str.split(","):
6168
feature_name_str = feature_name_str.strip()
62-
if not feature_name_str:
63-
continue
64-
try:
65-
feature_name = FeatureName(feature_name_str)
66-
override_feature_enabled(feature_name, True)
67-
except ValueError:
68-
valid_names = ", ".join(f.value for f in FeatureName)
69-
click.secho(
70-
f"WARNING: Unknown feature name '{feature_name_str}'. "
71-
f"Valid names are: {valid_names}",
72-
fg="yellow",
73-
err=True,
74-
)
69+
if feature_name_str:
70+
feature_overrides[feature_name_str] = True
71+
72+
for features_str in disable_features:
73+
for feature_name_str in features_str.split(","):
74+
feature_name_str = feature_name_str.strip()
75+
if feature_name_str:
76+
feature_overrides[feature_name_str] = False
77+
78+
# Apply all overrides
79+
for feature_name_str, enabled in feature_overrides.items():
80+
try:
81+
feature_name = FeatureName(feature_name_str)
82+
override_feature_enabled(feature_name, enabled)
83+
except ValueError:
84+
valid_names = ", ".join(f.value for f in FeatureName)
85+
click.secho(
86+
f"WARNING: Unknown feature name '{feature_name_str}'. "
87+
f"Valid names are: {valid_names}",
88+
fg="yellow",
89+
err=True,
90+
)
7591

7692

7793
def feature_options():
@@ -88,11 +104,25 @@ def decorator(func):
88104
),
89105
multiple=True,
90106
)
107+
@click.option(
108+
"--disable_features",
109+
help=(
110+
"Optional. Comma-separated list of feature names to disable. "
111+
"This provides an alternative to environment variables for "
112+
"disabling features. Example: "
113+
"--disable_features=JSON_SCHEMA_FOR_FUNC_DECL,PROGRESSIVE_SSE_STREAMING"
114+
),
115+
multiple=True,
116+
)
91117
@functools.wraps(func)
92118
def wrapper(*args, **kwargs):
93119
enable_features = kwargs.pop("enable_features", ())
94-
if enable_features:
95-
_apply_feature_overrides(enable_features)
120+
disable_features = kwargs.pop("disable_features", ())
121+
if enable_features or disable_features:
122+
_apply_feature_overrides(
123+
enable_features=enable_features,
124+
disable_features=disable_features,
125+
)
96126
return func(*args, **kwargs)
97127

98128
return wrapper
@@ -1751,6 +1781,14 @@ def cli_migrate_session(
17511781
default=None,
17521782
help="Optional. Whether to enable Cloud Trace for Agent Engine.",
17531783
)
1784+
@click.option(
1785+
"--otel_to_cloud",
1786+
type=bool,
1787+
is_flag=True,
1788+
show_default=True,
1789+
default=None,
1790+
help="Optional. Whether to enable OpenTelemetry for Agent Engine.",
1791+
)
17541792
@click.option(
17551793
"--display_name",
17561794
type=str,
@@ -1842,6 +1880,7 @@ def cli_deploy_agent_engine(
18421880
staging_bucket: Optional[str],
18431881
agent_engine_id: Optional[str],
18441882
trace_to_cloud: Optional[bool],
1883+
otel_to_cloud: Optional[bool],
18451884
api_key: Optional[str],
18461885
display_name: str,
18471886
description: str,
@@ -1872,6 +1911,7 @@ def cli_deploy_agent_engine(
18721911
region=region,
18731912
agent_engine_id=agent_engine_id,
18741913
trace_to_cloud=trace_to_cloud,
1914+
otel_to_cloud=otel_to_cloud,
18751915
api_key=api_key,
18761916
adk_app_object=adk_app_object,
18771917
display_name=display_name,

src/google/adk/sessions/migration/_schema_check_utils.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,28 @@ def get_db_schema_version_from_connection(connection) -> str:
8282
return _get_schema_version_impl(inspector, connection)
8383

8484

85-
def _to_sync_url(db_url: str) -> str:
86-
"""Removes '+driver' from SQLAlchemy URL."""
85+
def to_sync_url(db_url: str) -> str:
86+
"""Removes '+driver' from SQLAlchemy URL.
87+
88+
This is useful when you need to use a synchronous SQLAlchemy engine with
89+
a database URL that specifies an async driver (e.g., postgresql+asyncpg://
90+
or sqlite+aiosqlite://).
91+
92+
Args:
93+
db_url: The database URL, potentially with a driver specification.
94+
95+
Returns:
96+
The database URL with the driver specification removed (e.g.,
97+
'postgresql+asyncpg://host/db' becomes 'postgresql://host/db').
98+
99+
Examples:
100+
>>> to_sync_url('postgresql+asyncpg://localhost/mydb')
101+
'postgresql://localhost/mydb'
102+
>>> to_sync_url('sqlite+aiosqlite:///path/to/db.sqlite')
103+
'sqlite:///path/to/db.sqlite'
104+
>>> to_sync_url('mysql://localhost/mydb') # No driver, returns unchanged
105+
'mysql://localhost/mydb'
106+
"""
87107
if "://" in db_url:
88108
scheme, _, rest = db_url.partition("://")
89109
if "+" in scheme:
@@ -106,7 +126,7 @@ def get_db_schema_version(db_url: str) -> str:
106126
"""
107127
engine = None
108128
try:
109-
engine = create_sync_engine(_to_sync_url(db_url))
129+
engine = create_sync_engine(to_sync_url(db_url))
110130
with engine.connect() as connection:
111131
inspector = inspect(connection)
112132
return _get_schema_version_impl(inspector, connection)

src/google/adk/sessions/migration/migrate_from_sqlalchemy_pickle.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,17 +165,23 @@ def _get_state_dict(state_val: Any) -> dict:
165165
# --- Migration Logic ---
166166
def migrate(source_db_url: str, dest_db_url: str):
167167
"""Migrates data from old pickle schema to new JSON schema."""
168+
# Convert async driver URLs to sync URLs for SQLAlchemy's synchronous engine.
169+
# This allows users to provide URLs like 'postgresql+asyncpg://...' and have
170+
# them automatically converted to 'postgresql://...' for migration.
171+
source_sync_url = _schema_check_utils.to_sync_url(source_db_url)
172+
dest_sync_url = _schema_check_utils.to_sync_url(dest_db_url)
173+
168174
logger.info(f"Connecting to source database: {source_db_url}")
169175
try:
170-
source_engine = create_engine(source_db_url)
176+
source_engine = create_engine(source_sync_url)
171177
SourceSession = sessionmaker(bind=source_engine)
172178
except Exception as e:
173179
logger.error(f"Failed to connect to source database: {e}")
174180
raise RuntimeError(f"Failed to connect to source database: {e}") from e
175181

176182
logger.info(f"Connecting to destination database: {dest_db_url}")
177183
try:
178-
dest_engine = create_engine(dest_db_url)
184+
dest_engine = create_engine(dest_sync_url)
179185
v1.Base.metadata.create_all(dest_engine)
180186
DestSession = sessionmaker(bind=dest_engine)
181187
except Exception as e:

src/google/adk/sessions/migration/migrate_from_sqlalchemy_sqlite.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import sys
2424

2525
from google.adk.sessions import sqlite_session_service as sss
26+
from google.adk.sessions.migration import _schema_check_utils
2627
from google.adk.sessions.schemas import v0 as v0_schema
2728
from sqlalchemy import create_engine
2829
from sqlalchemy.orm import sessionmaker
@@ -32,9 +33,14 @@
3233

3334
def migrate(source_db_url: str, dest_db_path: str):
3435
"""Migrates data from a SQLAlchemy-based SQLite DB to the new schema."""
36+
# Convert async driver URLs to sync URLs for SQLAlchemy's synchronous engine.
37+
# This allows users to provide URLs like 'sqlite+aiosqlite://...' and have
38+
# them automatically converted to 'sqlite://...' for migration.
39+
source_sync_url = _schema_check_utils.to_sync_url(source_db_url)
40+
3541
logger.info(f"Connecting to source database: {source_db_url}")
3642
try:
37-
engine = create_engine(source_db_url)
43+
engine = create_engine(source_sync_url)
3844
v0_schema.Base.metadata.create_all(
3945
engine
4046
) # Ensure tables exist for inspection

src/google/adk/tools/load_artifacts_tool.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from google.genai import types
2525
from typing_extensions import override
2626

27+
from ..features import FeatureName
28+
from ..features import is_feature_enabled
2729
from .base_tool import BaseTool
2830

2931
# MIME types Gemini accepts for inline data in requests.
@@ -132,6 +134,20 @@ def __init__(self):
132134
)
133135

134136
def _get_declaration(self) -> types.FunctionDeclaration | None:
137+
if is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL):
138+
return types.FunctionDeclaration(
139+
name=self.name,
140+
description=self.description,
141+
parameters_json_schema={
142+
'type': 'object',
143+
'properties': {
144+
'artifact_names': {
145+
'type': 'array',
146+
'items': {'type': 'string'},
147+
},
148+
},
149+
},
150+
)
135151
return types.FunctionDeclaration(
136152
name=self.name,
137153
description=self.description,

src/google/adk/tools/load_memory_tool.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from pydantic import Field
2222
from typing_extensions import override
2323

24+
from ..features import FeatureName
25+
from ..features import is_feature_enabled
2426
from ..memory.memory_entry import MemoryEntry
2527
from .function_tool import FunctionTool
2628
from .tool_context import ToolContext
@@ -59,6 +61,18 @@ def __init__(self):
5961

6062
@override
6163
def _get_declaration(self) -> types.FunctionDeclaration | None:
64+
if is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL):
65+
return types.FunctionDeclaration(
66+
name=self.name,
67+
description=self.description,
68+
parameters_json_schema={
69+
'type': 'object',
70+
'properties': {
71+
'query': {'type': 'string'},
72+
},
73+
'required': ['query'],
74+
},
75+
)
6276
return types.FunctionDeclaration(
6377
name=self.name,
6478
description=self.description,

src/google/adk/tools/retrieval/base_retrieval_tool.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,34 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from __future__ import annotations
16+
1517
from google.genai import types
1618
from typing_extensions import override
1719

20+
from ...features import FeatureName
21+
from ...features import is_feature_enabled
1822
from ..base_tool import BaseTool
1923

2024

2125
class BaseRetrievalTool(BaseTool):
2226

2327
@override
2428
def _get_declaration(self) -> types.FunctionDeclaration:
29+
if is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL):
30+
return types.FunctionDeclaration(
31+
name=self.name,
32+
description=self.description,
33+
parameters_json_schema={
34+
'type': 'object',
35+
'properties': {
36+
'query': {
37+
'type': 'string',
38+
'description': 'The query to retrieve.',
39+
},
40+
},
41+
},
42+
)
2543
return types.FunctionDeclaration(
2644
name=self.name,
2745
description=self.description,

0 commit comments

Comments
 (0)