Skip to content

Commit d5f5951

Browse files
authored
Merge branch 'main' into add-customizable-memory_service-extension-for-get_fast_api_app
2 parents 117a849 + 3dd7e3f commit d5f5951

52 files changed

Lines changed: 2522 additions & 644 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/check-file-contents.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
echo ""
9797
9898
set +e
99-
FILES_WITH_FORBIDDEN_IMPORT=$(grep -lE '^from.*cli.*import.*$' $CHANGED_FILES)
99+
FILES_WITH_FORBIDDEN_IMPORT=$(grep -lE '^from.*\bcli\b.*import.*$' $CHANGED_FILES)
100100
GREP_EXIT_CODE=$?
101101
set -e
102102

.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:

contributing/samples/live_bidi_streaming_tools_agent/agent.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from google.adk.agents import LiveRequestQueue
1919
from google.adk.agents.llm_agent import Agent
2020
from google.adk.tools.function_tool import FunctionTool
21-
from google.genai import Client
2221
from google.genai import types as genai_types
2322

2423

@@ -54,6 +53,8 @@ async def monitor_video_stream(
5453
) -> AsyncGenerator[str, None]:
5554
"""Monitor how many people are in the video streams."""
5655
print("start monitor_video_stream!")
56+
from google.genai import Client
57+
5758
client = Client(vertexai=False)
5859
prompt_text = (
5960
"Count the number of people in this image. Just respond with a numeric"
@@ -87,7 +88,7 @@ async def monitor_video_stream(
8788

8889
# Call the model to generate content based on the provided image and prompt
8990
response = client.models.generate_content(
90-
model="gemini-2.0-flash-exp",
91+
model="gemini-2.5-flash",
9192
contents=contents,
9293
config=genai_types.GenerateContentConfig(
9394
system_instruction=(
@@ -121,9 +122,11 @@ def stop_streaming(function_name: str):
121122

122123

123124
root_agent = Agent(
124-
# find supported models here: https://google.github.io/adk-docs/get-started/streaming/quickstart-streaming/
125-
model="gemini-2.0-flash-live-preview-04-09", # for Vertex project
126-
# model="gemini-live-2.5-flash-preview", # for AI studio key
125+
# see https://docs.cloud.google.com/vertex-ai/generative-ai/docs/migrate
126+
# for vertex model names
127+
model="gemini-live-2.5-flash-native-audio", # vertex
128+
# see https://ai.google.dev/gemini-api/docs/models for AIS model names
129+
# model='gemini-2.5-flash-native-audio-latest', # for AI studio
127130
name="video_streaming_agent",
128131
instruction="""
129132
You are a monitoring agent. You can do video monitoring and stock price monitoring

contributing/samples/toolbox_agent/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ Install SQLite from [https://sqlite.org/](https://sqlite.org/)
2626

2727
### 3. Install Required Python Dependencies
2828

29-
**Important**: The ADK's `ToolboxToolset` class requires the `toolbox-core` package, which is not automatically installed with the ADK. Install it using:
29+
**Important**: The ADK's `ToolboxToolset` class requires the `toolbox-adk` package, which is not automatically installed with the ADK. Install it using:
3030

3131
```bash
32-
pip install toolbox-core
32+
pip install google-adk[toolbox]
3333
```
3434

3535
### 4. Create Database (Optional)

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,12 @@ extensions = [
157157
"llama-index-readers-file>=0.4.0", # For retrieval using LlamaIndex.
158158
"llama-index-embeddings-google-genai>=0.3.0", # For files retrieval using LlamaIndex.
159159
"lxml>=5.3.0", # For load_web_page tool.
160-
"toolbox-adk>=0.1.0", # For tools.toolbox_toolset.ToolboxToolset
160+
"toolbox-adk>=0.5.7, <0.6.0", # For tools.toolbox_toolset.ToolboxToolset
161161
]
162162

163163
otel-gcp = ["opentelemetry-instrumentation-google-genai>=0.3b0, <1.0.0"]
164164

165+
toolbox = ["toolbox-adk>=0.5.7, <0.6.0"]
165166

166167
[tool.pyink]
167168
# Format py files following Google style-guide

src/google/adk/a2a/converters/part_converter.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE = 'function_response'
4141
A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT = 'code_execution_result'
4242
A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE = 'executable_code'
43+
A2A_DATA_PART_TEXT_MIME_TYPE = 'text/plain'
44+
A2A_DATA_PART_START_TAG = b'<a2a_datapart_json>'
45+
A2A_DATA_PART_END_TAG = b'</a2a_datapart_json>'
4346

4447

4548
A2APartToGenAIPartConverter = Callable[
@@ -130,7 +133,16 @@ def convert_a2a_part_to_genai_part(
130133
part.data, by_alias=True
131134
)
132135
)
133-
return genai_types.Part(text=json.dumps(part.data))
136+
return genai_types.Part(
137+
inline_data=genai_types.Blob(
138+
data=A2A_DATA_PART_START_TAG
139+
+ part.model_dump_json(by_alias=True, exclude_none=True).encode(
140+
'utf-8'
141+
)
142+
+ A2A_DATA_PART_END_TAG,
143+
mime_type=A2A_DATA_PART_TEXT_MIME_TYPE,
144+
)
145+
)
134146

135147
logger.warning(
136148
'Cannot convert unsupported part type: %s for A2A part: %s',
@@ -163,6 +175,20 @@ def convert_genai_part_to_a2a_part(
163175
)
164176

165177
if part.inline_data:
178+
if (
179+
part.inline_data.mime_type == A2A_DATA_PART_TEXT_MIME_TYPE
180+
and part.inline_data.data is not None
181+
and part.inline_data.data.startswith(A2A_DATA_PART_START_TAG)
182+
and part.inline_data.data.endswith(A2A_DATA_PART_END_TAG)
183+
):
184+
return a2a_types.Part(
185+
root=a2a_types.DataPart.model_validate_json(
186+
part.inline_data.data[
187+
len(A2A_DATA_PART_START_TAG) : -len(A2A_DATA_PART_END_TAG)
188+
]
189+
)
190+
)
191+
# The default case for inline_data is to convert it to FileWithBytes.
166192
a2a_part = a2a_types.FilePart(
167193
file=a2a_types.FileWithBytes(
168194
bytes=base64.b64encode(part.inline_data.data).decode('utf-8'),

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 & 2 deletions
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:
@@ -436,7 +443,8 @@ async def _handle_a2a_response(
436443
and event.content is not None
437444
and event.content.parts
438445
):
439-
event.content.parts[0].thought = True
446+
for part in event.content.parts:
447+
part.thought = True
440448
elif (
441449
isinstance(update, A2ATaskStatusUpdateEvent)
442450
and update.status

0 commit comments

Comments
 (0)