Skip to content

Commit b633bab

Browse files
authored
Merge branch 'main' into feat/add-metadata-parameter
2 parents da3e685 + d0aef8a commit b633bab

22 files changed

+910
-465
lines changed

.github/workflows/stale-bot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ on:
2323

2424
jobs:
2525
audit-stale-issues:
26+
if: github.repository == 'google/adk-python'
2627
runs-on: ubuntu-latest
2728
timeout-minutes: 60
2829

.github/workflows/triage.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ jobs:
1515
# - New issues (need component labeling)
1616
# - Issues labeled with "planned" (need owner assignment)
1717
if: >-
18-
github.event_name == 'schedule' ||
19-
github.event.action == 'opened' ||
20-
github.event.label.name == 'planned'
18+
github.repository == 'google/adk-python' && (
19+
github.event_name == 'schedule' ||
20+
github.event.action == 'opened' ||
21+
github.event.label.name == 'planned'
22+
)
2123
permissions:
2224
issues: write
2325
contents: read

.github/workflows/upload-adk-docs-to-vertex-ai-search.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99

1010
jobs:
1111
upload-adk-docs-to-vertex-ai-search:
12+
if: github.repository == 'google/adk-python'
1213
runs-on: ubuntu-latest
1314

1415
steps:

src/google/adk/agents/llm_agent.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ class LlmAgent(BaseAgent):
285285
"""The additional content generation configurations.
286286
287287
NOTE: not all fields are usable, e.g. tools must be configured via `tools`,
288-
thinking_config must be configured via `planner` in LlmAgent.
288+
thinking_config can be configured here or via the `planner`. If both are set, the planner's configuration takes precedence.
289289
290290
For example: use this config to adjust model temperature, configure safety
291291
settings, etc.
@@ -849,8 +849,6 @@ def validate_generate_content_config(
849849
) -> types.GenerateContentConfig:
850850
if not generate_content_config:
851851
return types.GenerateContentConfig()
852-
if generate_content_config.thinking_config:
853-
raise ValueError('Thinking config should be set via LlmAgent.planner.')
854852
if generate_content_config.tools:
855853
raise ValueError('All tools must be set via LlmAgent.tools.')
856854
if generate_content_config.system_instruction:
@@ -863,6 +861,23 @@ def validate_generate_content_config(
863861
)
864862
return generate_content_config
865863

864+
@override
865+
def model_post_init(self, __context: Any) -> None:
866+
"""Provides a warning if multiple thinking configurations are found."""
867+
super().model_post_init(__context)
868+
869+
# Note: Using getattr to check both locations for thinking_config
870+
if getattr(
871+
self.generate_content_config, 'thinking_config', None
872+
) and getattr(self.planner, 'thinking_config', None):
873+
warnings.warn(
874+
'Both `thinking_config` in `generate_content_config` and a '
875+
'planner with `thinking_config` are provided. The '
876+
"planner's configuration will take precedence.",
877+
UserWarning,
878+
stacklevel=3,
879+
)
880+
866881
@classmethod
867882
@experimental
868883
def _resolve_tools(

src/google/adk/agents/remote_a2a_agent.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,13 @@ def _create_a2a_request_for_user_function_response(
348348

349349
return a2a_message
350350

351+
def _is_remote_response(self, event: Event) -> bool:
352+
return (
353+
event.author == self.name
354+
and event.custom_metadata
355+
and event.custom_metadata.get(A2A_METADATA_PREFIX + "response", False)
356+
)
357+
351358
def _construct_message_parts_from_session(
352359
self, ctx: InvocationContext
353360
) -> tuple[list[A2APart], Optional[str]]:
@@ -365,7 +372,7 @@ def _construct_message_parts_from_session(
365372

366373
events_to_process = []
367374
for event in reversed(ctx.session.events):
368-
if event.author == self.name:
375+
if self._is_remote_response(event):
369376
# stop on content generated by current a2a agent given it should already
370377
# be in remote session
371378
if event.custom_metadata:
@@ -496,6 +503,8 @@ async def _handle_a2a_response(
496503
invocation_id=ctx.invocation_id,
497504
branch=ctx.branch,
498505
)
506+
event.custom_metadata = event.custom_metadata or {}
507+
event.custom_metadata[A2A_METADATA_PREFIX + "response"] = True
499508
return event
500509
except A2AClientError as e:
501510
logger.error("Failed to handle A2A response: %s", e)

src/google/adk/cli/adk_web_server.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,8 +1558,17 @@ async def event_generator():
15581558
yield f"data: {sse_event}\n\n"
15591559
except Exception as e:
15601560
logger.exception("Error in event_generator: %s", e)
1561-
# You might want to yield an error event here
1562-
yield f'data: {{"error": "{str(e)}"}}\n\n'
1561+
# Yield a proper Event object for the error
1562+
error_event = Event(
1563+
author="system",
1564+
content=types.Content(
1565+
role="model", parts=[types.Part(text=f"Error: {e}")]
1566+
),
1567+
)
1568+
yield (
1569+
"data:"
1570+
f" {error_event.model_dump_json(by_alias=True, exclude_none=True)}\n\n"
1571+
)
15631572

15641573
# Returns a streaming response with the proper media type for SSE
15651574
return StreamingResponse(

src/google/adk/cli/cli_deploy.py

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,44 @@
2020
import subprocess
2121
from typing import Final
2222
from typing import Optional
23+
import warnings
2324

2425
import click
2526
from packaging.version import parse
2627

2728
_IS_WINDOWS = os.name == 'nt'
2829
_GCLOUD_CMD = 'gcloud.cmd' if _IS_WINDOWS else 'gcloud'
2930
_LOCAL_STORAGE_FLAG_MIN_VERSION: Final[str] = '1.21.0'
31+
_AGENT_ENGINE_REQUIREMENT: Final[str] = (
32+
'google-cloud-aiplatform[adk,agent_engines]'
33+
)
34+
35+
36+
def _ensure_agent_engine_dependency(requirements_txt_path: str) -> None:
37+
"""Ensures staged requirements include Agent Engine dependencies."""
38+
if not os.path.exists(requirements_txt_path):
39+
raise FileNotFoundError(
40+
f'requirements.txt not found at: {requirements_txt_path}'
41+
)
42+
43+
requirements = ''
44+
with open(requirements_txt_path, 'r', encoding='utf-8') as f:
45+
requirements = f.read()
46+
47+
for line in requirements.splitlines():
48+
stripped = line.strip()
49+
if (
50+
stripped
51+
and not stripped.startswith('#')
52+
and stripped.startswith('google-cloud-aiplatform')
53+
):
54+
return
55+
56+
with open(requirements_txt_path, 'a', encoding='utf-8') as f:
57+
if requirements and not requirements.endswith('\n'):
58+
f.write('\n')
59+
f.write(_AGENT_ENGINE_REQUIREMENT + '\n')
60+
3061

3162
_DOCKERFILE_TEMPLATE: Final[str] = """
3263
FROM python:3.11-slim
@@ -74,12 +105,7 @@
74105
75106
if {is_config_agent}:
76107
from google.adk.agents import config_agent_utils
77-
try:
78-
# This path is for local loading.
79-
root_agent = config_agent_utils.from_config("{agent_folder}/root_agent.yaml")
80-
except FileNotFoundError:
81-
# This path is used to support the file structure in Agent Engine.
82-
root_agent = config_agent_utils.from_config("./{temp_folder}/{app_name}/root_agent.yaml")
108+
root_agent = config_agent_utils.from_config("{agent_folder}/root_agent.yaml")
83109
else:
84110
from .agent import {adk_app_object}
85111
@@ -661,7 +687,7 @@ def to_agent_engine(
661687
agent_folder: str,
662688
temp_folder: Optional[str] = None,
663689
adk_app: str,
664-
staging_bucket: str,
690+
staging_bucket: Optional[str] = None,
665691
trace_to_cloud: Optional[bool] = None,
666692
api_key: Optional[str] = None,
667693
adk_app_object: Optional[str] = None,
@@ -704,7 +730,8 @@ def to_agent_engine(
704730
files. It will be replaced with the generated files if it already exists.
705731
adk_app (str): The name of the file (without .py) containing the AdkApp
706732
instance.
707-
staging_bucket (str): The GCS bucket for staging the deployment artifacts.
733+
staging_bucket (str): Deprecated. This argument is no longer required or
734+
used.
708735
trace_to_cloud (bool): Whether to enable Cloud Trace.
709736
api_key (str): Optional. The API key to use for Express Mode.
710737
If not provided, the API key from the GOOGLE_API_KEY environment variable
@@ -734,26 +761,41 @@ def to_agent_engine(
734761
app_name = os.path.basename(agent_folder)
735762
display_name = display_name or app_name
736763
parent_folder = os.path.dirname(agent_folder)
737-
if parent_folder != os.getcwd():
738-
click.echo(f'Please deploy from the project dir: {parent_folder}')
739-
return
740-
tmp_app_name = app_name + '_tmp' + datetime.now().strftime('%Y%m%d_%H%M%S')
741-
temp_folder = temp_folder or tmp_app_name
742-
agent_src_path = os.path.join(parent_folder, temp_folder)
743-
click.echo(f'Staging all files in: {agent_src_path}')
744764
adk_app_object = adk_app_object or 'root_agent'
745765
if adk_app_object not in ['root_agent', 'app']:
746766
click.echo(
747767
f'Invalid adk_app_object: {adk_app_object}. Please use "root_agent"'
748768
' or "app".'
749769
)
750770
return
771+
if staging_bucket:
772+
warnings.warn(
773+
'WARNING: `staging_bucket` is deprecated and will be removed in a'
774+
' future release. Please drop it from the list of arguments.',
775+
DeprecationWarning,
776+
stacklevel=2,
777+
)
778+
779+
original_cwd = os.getcwd()
780+
did_change_cwd = False
781+
if parent_folder != original_cwd:
782+
click.echo(
783+
'Agent Engine deployment uses relative paths; temporarily switching '
784+
f'working directory to: {parent_folder}'
785+
)
786+
os.chdir(parent_folder)
787+
did_change_cwd = True
788+
tmp_app_name = app_name + '_tmp' + datetime.now().strftime('%Y%m%d_%H%M%S')
789+
temp_folder = temp_folder or tmp_app_name
790+
agent_src_path = os.path.join(parent_folder, temp_folder)
791+
click.echo(f'Staging all files in: {agent_src_path}')
751792
# remove agent_src_path if it exists
752793
if os.path.exists(agent_src_path):
753794
click.echo('Removing existing files')
754795
shutil.rmtree(agent_src_path)
755796

756797
try:
798+
click.echo(f'Staging all files in: {agent_src_path}')
757799
ignore_patterns = None
758800
ae_ignore_path = os.path.join(agent_folder, '.ae_ignore')
759801
if os.path.exists(ae_ignore_path):
@@ -762,15 +804,18 @@ def to_agent_engine(
762804
patterns = [pattern.strip() for pattern in f.readlines()]
763805
ignore_patterns = shutil.ignore_patterns(*patterns)
764806
click.echo('Copying agent source code...')
765-
shutil.copytree(agent_folder, agent_src_path, ignore=ignore_patterns)
807+
shutil.copytree(
808+
agent_folder,
809+
agent_src_path,
810+
ignore=ignore_patterns,
811+
dirs_exist_ok=True,
812+
)
766813
click.echo('Copying agent source code complete.')
767814

768815
project = _resolve_project(project)
769816

770817
click.echo('Resolving files and dependencies...')
771818
agent_config = {}
772-
if staging_bucket:
773-
agent_config['staging_bucket'] = staging_bucket
774819
if not agent_engine_config_file:
775820
# Attempt to read the agent engine config from .agent_engine_config.json in the dir (if any).
776821
agent_engine_config_file = os.path.join(
@@ -813,8 +858,9 @@ def to_agent_engine(
813858
if not os.path.exists(requirements_txt_path):
814859
click.echo(f'Creating {requirements_txt_path}...')
815860
with open(requirements_txt_path, 'w', encoding='utf-8') as f:
816-
f.write('google-cloud-aiplatform[adk,agent_engines]')
861+
f.write(_AGENT_ENGINE_REQUIREMENT + '\n')
817862
click.echo(f'Created {requirements_txt_path}')
863+
_ensure_agent_engine_dependency(requirements_txt_path)
818864
agent_config['requirements_file'] = f'{temp_folder}/requirements.txt'
819865

820866
env_vars = {}
@@ -912,8 +958,7 @@ def to_agent_engine(
912958
app_name=app_name,
913959
trace_to_cloud_option=trace_to_cloud,
914960
is_config_agent=is_config_agent,
915-
temp_folder=temp_folder,
916-
agent_folder=agent_folder,
961+
agent_folder=f'./{temp_folder}',
917962
adk_app_object=adk_app_object,
918963
adk_app_type=adk_app_type,
919964
express_mode=api_key is not None,
@@ -946,7 +991,9 @@ def to_agent_engine(
946991
click.secho(f'✅ Updated agent engine: {agent_engine_id}', fg='green')
947992
finally:
948993
click.echo(f'Cleaning up the temp folder: {temp_folder}')
949-
shutil.rmtree(temp_folder)
994+
shutil.rmtree(agent_src_path)
995+
if did_change_cwd:
996+
os.chdir(original_cwd)
950997

951998

952999
def to_gke(

src/google/adk/cli/cli_tools_click.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,19 @@ def wrapper(*args, **kwargs):
10311031
return decorator
10321032

10331033

1034+
def _deprecate_staging_bucket(ctx, param, value):
1035+
if value:
1036+
click.echo(
1037+
click.style(
1038+
f"WARNING: --{param} is deprecated and will be removed. Please"
1039+
" leave it unspecified.",
1040+
fg="yellow",
1041+
),
1042+
err=True,
1043+
)
1044+
return value
1045+
1046+
10341047
def deprecated_adk_services_options():
10351048
"""Deprecated ADK services options."""
10361049

@@ -1689,10 +1702,8 @@ def cli_migrate_session(
16891702
"--staging_bucket",
16901703
type=str,
16911704
default=None,
1692-
help=(
1693-
"Optional. GCS bucket for staging the deployment artifacts. It will be"
1694-
" ignored if api_key is set."
1695-
),
1705+
help="Deprecated. This argument is no longer required or used.",
1706+
callback=_deprecate_staging_bucket,
16961707
)
16971708
@click.option(
16981709
"--agent_engine_id",
@@ -1827,16 +1838,14 @@ def cli_deploy_agent_engine(
18271838
18281839
# With Google Cloud Project and Region
18291840
adk deploy agent_engine --project=[project] --region=[region]
1830-
--staging_bucket=[staging_bucket] --display_name=[app_name]
1831-
my_agent
1841+
--display_name=[app_name] my_agent
18321842
"""
18331843
logging.getLogger("vertexai_genai.agentengines").setLevel(logging.INFO)
18341844
try:
18351845
cli_deploy.to_agent_engine(
18361846
agent_folder=agent,
18371847
project=project,
18381848
region=region,
1839-
staging_bucket=staging_bucket,
18401849
agent_engine_id=agent_engine_id,
18411850
trace_to_cloud=trace_to_cloud,
18421851
api_key=api_key,

src/google/adk/planners/built_in_planner.py

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

15+
from __future__ import annotations
16+
17+
import logging
1518
from typing import List
1619
from typing import Optional
1720

@@ -23,6 +26,8 @@
2326
from ..models.llm_request import LlmRequest
2427
from .base_planner import BasePlanner
2528

29+
logger = logging.getLogger('google_adk.' + __name__)
30+
2631

2732
class BuiltInPlanner(BasePlanner):
2833
"""The built-in planner that uses model's built-in thinking features.
@@ -57,6 +62,11 @@ def apply_thinking_config(self, llm_request: LlmRequest) -> None:
5762
"""
5863
if self.thinking_config:
5964
llm_request.config = llm_request.config or types.GenerateContentConfig()
65+
if llm_request.config.thinking_config:
66+
logger.debug(
67+
'Overwriting `thinking_config` from `generate_content_config` with '
68+
'the one provided by the `BuiltInPlanner`.'
69+
)
6070
llm_request.config.thinking_config = self.thinking_config
6171

6272
@override

0 commit comments

Comments
 (0)