google-adk 1.6.1__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- google/adk/a2a/converters/event_converter.py +5 -85
- google/adk/a2a/executor/a2a_agent_executor.py +45 -16
- google/adk/agents/__init__.py +5 -0
- google/adk/agents/agent_config.py +46 -0
- google/adk/agents/base_agent.py +234 -41
- google/adk/agents/callback_context.py +41 -0
- google/adk/agents/common_configs.py +79 -0
- google/adk/agents/config_agent_utils.py +184 -0
- google/adk/agents/config_schemas/AgentConfig.json +544 -0
- google/adk/agents/invocation_context.py +5 -1
- google/adk/agents/llm_agent.py +190 -9
- google/adk/agents/loop_agent.py +29 -0
- google/adk/agents/parallel_agent.py +24 -3
- google/adk/agents/remote_a2a_agent.py +15 -3
- google/adk/agents/sequential_agent.py +22 -1
- google/adk/artifacts/gcs_artifact_service.py +24 -2
- google/adk/auth/auth_handler.py +3 -3
- google/adk/auth/credential_manager.py +23 -23
- google/adk/auth/credential_service/base_credential_service.py +6 -6
- google/adk/auth/credential_service/in_memory_credential_service.py +10 -8
- google/adk/auth/credential_service/session_state_credential_service.py +8 -8
- google/adk/auth/exchanger/oauth2_credential_exchanger.py +3 -3
- google/adk/auth/oauth2_credential_util.py +2 -2
- google/adk/auth/refresher/oauth2_credential_refresher.py +4 -4
- google/adk/cli/agent_graph.py +3 -1
- google/adk/cli/browser/index.html +1 -1
- google/adk/cli/browser/main-SRBSE46V.js +3914 -0
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/fast_api.py +42 -2
- google/adk/cli/utils/agent_loader.py +35 -1
- google/adk/code_executors/base_code_executor.py +14 -19
- google/adk/code_executors/built_in_code_executor.py +4 -1
- google/adk/evaluation/base_eval_service.py +46 -2
- google/adk/evaluation/evaluation_generator.py +1 -1
- google/adk/evaluation/in_memory_eval_sets_manager.py +151 -0
- google/adk/evaluation/local_eval_service.py +389 -0
- google/adk/evaluation/local_eval_sets_manager.py +23 -8
- google/adk/flows/llm_flows/auto_flow.py +6 -11
- google/adk/flows/llm_flows/base_llm_flow.py +41 -23
- google/adk/flows/llm_flows/contents.py +16 -10
- google/adk/flows/llm_flows/functions.py +76 -33
- google/adk/memory/in_memory_memory_service.py +20 -14
- google/adk/models/anthropic_llm.py +44 -5
- google/adk/models/google_llm.py +11 -6
- google/adk/models/lite_llm.py +21 -4
- google/adk/plugins/__init__.py +17 -0
- google/adk/plugins/base_plugin.py +317 -0
- google/adk/plugins/plugin_manager.py +265 -0
- google/adk/runners.py +122 -18
- google/adk/sessions/database_session_service.py +26 -28
- google/adk/sessions/vertex_ai_session_service.py +14 -7
- google/adk/tools/agent_tool.py +1 -0
- google/adk/tools/apihub_tool/apihub_toolset.py +38 -39
- google/adk/tools/application_integration_tool/application_integration_toolset.py +35 -37
- google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -3
- google/adk/tools/base_tool.py +9 -9
- google/adk/tools/base_toolset.py +7 -5
- google/adk/tools/bigquery/__init__.py +3 -3
- google/adk/tools/enterprise_search_tool.py +4 -2
- google/adk/tools/google_api_tool/google_api_tool.py +16 -1
- google/adk/tools/google_api_tool/google_api_toolset.py +9 -7
- google/adk/tools/google_api_tool/google_api_toolsets.py +41 -20
- google/adk/tools/google_search_tool.py +4 -2
- google/adk/tools/langchain_tool.py +2 -3
- google/adk/tools/long_running_tool.py +21 -0
- google/adk/tools/mcp_tool/mcp_toolset.py +27 -28
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +8 -8
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +4 -6
- google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +3 -2
- google/adk/tools/tool_context.py +0 -10
- google/adk/tools/url_context_tool.py +4 -2
- google/adk/tools/vertex_ai_search_tool.py +4 -2
- google/adk/utils/model_name_utils.py +90 -0
- google/adk/version.py +1 -1
- {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/METADATA +2 -2
- {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/RECORD +79 -69
- google/adk/cli/browser/main-RXDVX3K6.js +0 -3914
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
- {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/WHEEL +0 -0
- {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/entry_points.txt +0 -0
- {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -153,37 +153,67 @@ async def handle_function_calls_async(
|
|
153
153
|
# do not use "args" as the variable name, because it is a reserved keyword
|
154
154
|
# in python debugger.
|
155
155
|
function_args = function_call.args or {}
|
156
|
-
function_response: Optional[dict] = None
|
157
156
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
break
|
157
|
+
# Step 1: Check if plugin before_tool_callback overrides the function
|
158
|
+
# response.
|
159
|
+
function_response = (
|
160
|
+
await invocation_context.plugin_manager.run_before_tool_callback(
|
161
|
+
tool=tool, tool_args=function_args, tool_context=tool_context
|
162
|
+
)
|
163
|
+
)
|
166
164
|
|
167
|
-
|
165
|
+
# Step 2: If no overrides are provided from the plugins, further run the
|
166
|
+
# canonical callback.
|
167
|
+
if function_response is None:
|
168
|
+
for callback in agent.canonical_before_tool_callbacks:
|
169
|
+
function_response = callback(
|
170
|
+
tool=tool, args=function_args, tool_context=tool_context
|
171
|
+
)
|
172
|
+
if inspect.isawaitable(function_response):
|
173
|
+
function_response = await function_response
|
174
|
+
if function_response:
|
175
|
+
break
|
176
|
+
|
177
|
+
# Step 3: Otherwise, proceed calling the tool normally.
|
178
|
+
if function_response is None:
|
168
179
|
function_response = await __call_tool_async(
|
169
180
|
tool, args=function_args, tool_context=tool_context
|
170
181
|
)
|
171
182
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
183
|
+
# Step 4: Check if plugin after_tool_callback overrides the function
|
184
|
+
# response.
|
185
|
+
altered_function_response = (
|
186
|
+
await invocation_context.plugin_manager.run_after_tool_callback(
|
187
|
+
tool=tool,
|
188
|
+
tool_args=function_args,
|
189
|
+
tool_context=tool_context,
|
190
|
+
result=function_response,
|
191
|
+
)
|
192
|
+
)
|
193
|
+
|
194
|
+
# Step 5: If no overrides are provided from the plugins, further run the
|
195
|
+
# canonical after_tool_callbacks.
|
196
|
+
if altered_function_response is None:
|
197
|
+
for callback in agent.canonical_after_tool_callbacks:
|
198
|
+
altered_function_response = callback(
|
199
|
+
tool=tool,
|
200
|
+
args=function_args,
|
201
|
+
tool_context=tool_context,
|
202
|
+
tool_response=function_response,
|
203
|
+
)
|
204
|
+
if inspect.isawaitable(altered_function_response):
|
205
|
+
altered_function_response = await altered_function_response
|
206
|
+
if altered_function_response:
|
207
|
+
break
|
208
|
+
|
209
|
+
# Step 6: If alternative response exists from after_tool_callback, use it
|
210
|
+
# instead of the original function response.
|
211
|
+
if altered_function_response is not None:
|
212
|
+
function_response = altered_function_response
|
184
213
|
|
185
214
|
if tool.is_long_running:
|
186
|
-
# Allow long running function to return None to not provide function
|
215
|
+
# Allow long running function to return None to not provide function
|
216
|
+
# response.
|
187
217
|
if not function_response:
|
188
218
|
continue
|
189
219
|
|
@@ -264,6 +294,7 @@ async def handle_function_calls_live(
|
|
264
294
|
# )
|
265
295
|
# if new_response:
|
266
296
|
# function_response = new_response
|
297
|
+
altered_function_response = None
|
267
298
|
if agent.after_tool_callback:
|
268
299
|
altered_function_response = agent.after_tool_callback(
|
269
300
|
tool=tool,
|
@@ -273,8 +304,8 @@ async def handle_function_calls_live(
|
|
273
304
|
)
|
274
305
|
if inspect.isawaitable(altered_function_response):
|
275
306
|
altered_function_response = await altered_function_response
|
276
|
-
|
277
|
-
|
307
|
+
if altered_function_response is not None:
|
308
|
+
function_response = altered_function_response
|
278
309
|
|
279
310
|
if tool.is_long_running:
|
280
311
|
# Allow async function to return None to not provide function response.
|
@@ -480,6 +511,16 @@ def __build_response_event(
|
|
480
511
|
return function_response_event
|
481
512
|
|
482
513
|
|
514
|
+
def deep_merge_dicts(d1: dict, d2: dict) -> dict:
|
515
|
+
"""Recursively merges d2 into d1."""
|
516
|
+
for key, value in d2.items():
|
517
|
+
if key in d1 and isinstance(d1[key], dict) and isinstance(value, dict):
|
518
|
+
d1[key] = deep_merge_dicts(d1[key], value)
|
519
|
+
else:
|
520
|
+
d1[key] = value
|
521
|
+
return d1
|
522
|
+
|
523
|
+
|
483
524
|
def merge_parallel_function_response_events(
|
484
525
|
function_response_events: list['Event'],
|
485
526
|
) -> 'Event':
|
@@ -498,15 +539,17 @@ def merge_parallel_function_response_events(
|
|
498
539
|
base_event = function_response_events[0]
|
499
540
|
|
500
541
|
# Merge actions from all events
|
501
|
-
|
502
|
-
merged_actions = EventActions()
|
503
|
-
merged_requested_auth_configs = {}
|
542
|
+
merged_actions_data = {}
|
504
543
|
for event in function_response_events:
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
544
|
+
if event.actions:
|
545
|
+
# Use `by_alias=True` because it converts the model to a dictionary while respecting field aliases, ensuring that the enum fields are correctly handled without creating a duplicate.
|
546
|
+
merged_actions_data = deep_merge_dicts(
|
547
|
+
merged_actions_data,
|
548
|
+
event.actions.model_dump(exclude_none=True, by_alias=True),
|
549
|
+
)
|
550
|
+
|
551
|
+
merged_actions = EventActions.model_validate(merged_actions_data)
|
552
|
+
|
510
553
|
# Create the new merged event
|
511
554
|
merged_event = Event(
|
512
555
|
invocation_id=Event.new_id(),
|
@@ -14,6 +14,7 @@
|
|
14
14
|
from __future__ import annotations
|
15
15
|
|
16
16
|
import re
|
17
|
+
import threading
|
17
18
|
from typing import TYPE_CHECKING
|
18
19
|
|
19
20
|
from typing_extensions import override
|
@@ -42,38 +43,43 @@ class InMemoryMemoryService(BaseMemoryService):
|
|
42
43
|
|
43
44
|
Uses keyword matching instead of semantic search.
|
44
45
|
|
45
|
-
|
46
|
-
|
46
|
+
This class is thread-safe, however, it should be used for testing and
|
47
|
+
development only.
|
47
48
|
"""
|
48
49
|
|
49
50
|
def __init__(self):
|
51
|
+
self._lock = threading.Lock()
|
52
|
+
|
50
53
|
self._session_events: dict[str, dict[str, list[Event]]] = {}
|
51
|
-
"""Keys are app_name/user_id
|
54
|
+
"""Keys are "{app_name}/{user_id}". Values are dicts of session_id to
|
55
|
+
session event lists.
|
56
|
+
"""
|
52
57
|
|
53
58
|
@override
|
54
59
|
async def add_session_to_memory(self, session: Session):
|
55
60
|
user_key = _user_key(session.app_name, session.user_id)
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
|
62
|
+
with self._lock:
|
63
|
+
self._session_events[user_key] = self._session_events.get(user_key, {})
|
64
|
+
self._session_events[user_key][session.id] = [
|
65
|
+
event
|
66
|
+
for event in session.events
|
67
|
+
if event.content and event.content.parts
|
68
|
+
]
|
64
69
|
|
65
70
|
@override
|
66
71
|
async def search_memory(
|
67
72
|
self, *, app_name: str, user_id: str, query: str
|
68
73
|
) -> SearchMemoryResponse:
|
69
74
|
user_key = _user_key(app_name, user_id)
|
70
|
-
|
71
|
-
|
75
|
+
|
76
|
+
with self._lock:
|
77
|
+
session_event_lists = self._session_events.get(user_key, {})
|
72
78
|
|
73
79
|
words_in_query = set(query.lower().split())
|
74
80
|
response = SearchMemoryResponse()
|
75
81
|
|
76
|
-
for session_events in
|
82
|
+
for session_events in session_event_lists.values():
|
77
83
|
for event in session_events:
|
78
84
|
if not event.content or not event.content.parts:
|
79
85
|
continue
|
@@ -16,6 +16,7 @@
|
|
16
16
|
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
|
+
import base64
|
19
20
|
from functools import cached_property
|
20
21
|
import logging
|
21
22
|
import os
|
@@ -45,7 +46,7 @@ __all__ = ["Claude"]
|
|
45
46
|
|
46
47
|
logger = logging.getLogger("google_adk." + __name__)
|
47
48
|
|
48
|
-
MAX_TOKEN =
|
49
|
+
MAX_TOKEN = 8192
|
49
50
|
|
50
51
|
|
51
52
|
class ClaudeRequest(BaseModel):
|
@@ -70,6 +71,14 @@ def to_google_genai_finish_reason(
|
|
70
71
|
return "FINISH_REASON_UNSPECIFIED"
|
71
72
|
|
72
73
|
|
74
|
+
def _is_image_part(part: types.Part) -> bool:
|
75
|
+
return (
|
76
|
+
part.inline_data
|
77
|
+
and part.inline_data.mime_type
|
78
|
+
and part.inline_data.mime_type.startswith("image")
|
79
|
+
)
|
80
|
+
|
81
|
+
|
73
82
|
def part_to_message_block(
|
74
83
|
part: types.Part,
|
75
84
|
) -> Union[
|
@@ -80,7 +89,7 @@ def part_to_message_block(
|
|
80
89
|
]:
|
81
90
|
if part.text:
|
82
91
|
return anthropic_types.TextBlockParam(text=part.text, type="text")
|
83
|
-
|
92
|
+
elif part.function_call:
|
84
93
|
assert part.function_call.name
|
85
94
|
|
86
95
|
return anthropic_types.ToolUseBlockParam(
|
@@ -89,7 +98,7 @@ def part_to_message_block(
|
|
89
98
|
input=part.function_call.args,
|
90
99
|
type="tool_use",
|
91
100
|
)
|
92
|
-
|
101
|
+
elif part.function_response:
|
93
102
|
content = ""
|
94
103
|
if (
|
95
104
|
"result" in part.function_response.response
|
@@ -105,15 +114,45 @@ def part_to_message_block(
|
|
105
114
|
content=content,
|
106
115
|
is_error=False,
|
107
116
|
)
|
108
|
-
|
117
|
+
elif _is_image_part(part):
|
118
|
+
data = base64.b64encode(part.inline_data.data).decode()
|
119
|
+
return anthropic_types.ImageBlockParam(
|
120
|
+
type="image",
|
121
|
+
source=dict(
|
122
|
+
type="base64", media_type=part.inline_data.mime_type, data=data
|
123
|
+
),
|
124
|
+
)
|
125
|
+
elif part.executable_code:
|
126
|
+
return anthropic_types.TextBlockParam(
|
127
|
+
type="text",
|
128
|
+
text="Code:```python\n" + part.executable_code.code + "\n```",
|
129
|
+
)
|
130
|
+
elif part.code_execution_result:
|
131
|
+
return anthropic_types.TextBlockParam(
|
132
|
+
text="Execution Result:```code_output\n"
|
133
|
+
+ part.code_execution_result.output
|
134
|
+
+ "\n```",
|
135
|
+
type="text",
|
136
|
+
)
|
137
|
+
|
138
|
+
raise NotImplementedError(f"Not supported yet: {part}")
|
109
139
|
|
110
140
|
|
111
141
|
def content_to_message_param(
|
112
142
|
content: types.Content,
|
113
143
|
) -> anthropic_types.MessageParam:
|
144
|
+
message_block = []
|
145
|
+
for part in content.parts or []:
|
146
|
+
# Image data is not supported in Claude for model turns.
|
147
|
+
if _is_image_part(part):
|
148
|
+
logger.warning("Image data is not supported in Claude for model turns.")
|
149
|
+
continue
|
150
|
+
|
151
|
+
message_block.append(part_to_message_block(part))
|
152
|
+
|
114
153
|
return {
|
115
154
|
"role": to_claude_role(content.role),
|
116
|
-
"content":
|
155
|
+
"content": message_block,
|
117
156
|
}
|
118
157
|
|
119
158
|
|
google/adk/models/google_llm.py
CHANGED
@@ -27,6 +27,7 @@ from typing import Union
|
|
27
27
|
|
28
28
|
from google.genai import Client
|
29
29
|
from google.genai import types
|
30
|
+
from google.genai.types import FinishReason
|
30
31
|
from typing_extensions import override
|
31
32
|
|
32
33
|
from .. import version
|
@@ -150,12 +151,10 @@ class Gemini(BaseLlm):
|
|
150
151
|
thought_text = ''
|
151
152
|
text = ''
|
152
153
|
yield llm_response
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
and response.candidates[0].finish_reason == types.FinishReason.STOP
|
158
|
-
):
|
154
|
+
|
155
|
+
# generate an aggregated content at the end regardless the
|
156
|
+
# response.candidates[0].finish_reason
|
157
|
+
if (text or thought_text) and response and response.candidates:
|
159
158
|
parts = []
|
160
159
|
if thought_text:
|
161
160
|
parts.append(types.Part(text=thought_text, thought=True))
|
@@ -163,6 +162,12 @@ class Gemini(BaseLlm):
|
|
163
162
|
parts.append(types.Part.from_text(text=text))
|
164
163
|
yield LlmResponse(
|
165
164
|
content=types.ModelContent(parts=parts),
|
165
|
+
error_code=None
|
166
|
+
if response.candidates[0].finish_reason == FinishReason.STOP
|
167
|
+
else response.candidates[0].finish_reason,
|
168
|
+
error_message=None
|
169
|
+
if response.candidates[0].finish_reason == FinishReason.STOP
|
170
|
+
else response.candidates[0].finish_message,
|
166
171
|
usage_metadata=usage_metadata,
|
167
172
|
)
|
168
173
|
|
google/adk/models/lite_llm.py
CHANGED
@@ -35,11 +35,14 @@ from litellm import acompletion
|
|
35
35
|
from litellm import ChatCompletionAssistantMessage
|
36
36
|
from litellm import ChatCompletionAssistantToolCall
|
37
37
|
from litellm import ChatCompletionDeveloperMessage
|
38
|
+
from litellm import ChatCompletionFileObject
|
39
|
+
from litellm import ChatCompletionImageObject
|
38
40
|
from litellm import ChatCompletionImageUrlObject
|
39
41
|
from litellm import ChatCompletionMessageToolCall
|
40
42
|
from litellm import ChatCompletionTextObject
|
41
43
|
from litellm import ChatCompletionToolMessage
|
42
44
|
from litellm import ChatCompletionUserMessage
|
45
|
+
from litellm import ChatCompletionVideoObject
|
43
46
|
from litellm import ChatCompletionVideoUrlObject
|
44
47
|
from litellm import completion
|
45
48
|
from litellm import CustomStreamWrapper
|
@@ -249,17 +252,31 @@ def _get_content(
|
|
249
252
|
data_uri = f"data:{part.inline_data.mime_type};base64,{base64_string}"
|
250
253
|
|
251
254
|
if part.inline_data.mime_type.startswith("image"):
|
255
|
+
# Extract format from mime type (e.g., "image/png" -> "png")
|
256
|
+
format_type = part.inline_data.mime_type.split("/")[-1]
|
252
257
|
content_objects.append(
|
253
|
-
|
258
|
+
ChatCompletionImageObject(
|
254
259
|
type="image_url",
|
255
|
-
image_url=
|
260
|
+
image_url=ChatCompletionImageUrlObject(
|
261
|
+
url=data_uri, format=format_type
|
262
|
+
),
|
256
263
|
)
|
257
264
|
)
|
258
265
|
elif part.inline_data.mime_type.startswith("video"):
|
266
|
+
# Extract format from mime type (e.g., "video/mp4" -> "mp4")
|
267
|
+
format_type = part.inline_data.mime_type.split("/")[-1]
|
259
268
|
content_objects.append(
|
260
|
-
|
269
|
+
ChatCompletionVideoObject(
|
261
270
|
type="video_url",
|
262
|
-
video_url=
|
271
|
+
video_url=ChatCompletionVideoUrlObject(
|
272
|
+
url=data_uri, format=format_type
|
273
|
+
),
|
274
|
+
)
|
275
|
+
)
|
276
|
+
elif part.inline_data.mime_type == "application/pdf":
|
277
|
+
content_objects.append(
|
278
|
+
ChatCompletionFileObject(
|
279
|
+
type="file", file={"file_data": data_uri, "format": "pdf"}
|
263
280
|
)
|
264
281
|
)
|
265
282
|
else:
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Copyright 2025 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 in 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
|
+
from .base_plugin import BasePlugin
|
16
|
+
|
17
|
+
__all__ = ['BasePlugin']
|