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
@@ -24,13 +24,11 @@ from typing import Optional
|
|
24
24
|
import uuid
|
25
25
|
|
26
26
|
from a2a.server.events import Event as A2AEvent
|
27
|
-
from a2a.types import Artifact
|
28
27
|
from a2a.types import DataPart
|
29
28
|
from a2a.types import Message
|
30
29
|
from a2a.types import Part as A2APart
|
31
30
|
from a2a.types import Role
|
32
31
|
from a2a.types import Task
|
33
|
-
from a2a.types import TaskArtifactUpdateEvent
|
34
32
|
from a2a.types import TaskState
|
35
33
|
from a2a.types import TaskStatus
|
36
34
|
from a2a.types import TaskStatusUpdateEvent
|
@@ -145,81 +143,6 @@ def _create_artifact_id(
|
|
145
143
|
return ARTIFACT_ID_SEPARATOR.join(components)
|
146
144
|
|
147
145
|
|
148
|
-
def _convert_artifact_to_a2a_events(
|
149
|
-
event: Event,
|
150
|
-
invocation_context: InvocationContext,
|
151
|
-
filename: str,
|
152
|
-
version: int,
|
153
|
-
task_id: Optional[str] = None,
|
154
|
-
context_id: Optional[str] = None,
|
155
|
-
) -> TaskArtifactUpdateEvent:
|
156
|
-
"""Converts a new artifact version to an A2A TaskArtifactUpdateEvent.
|
157
|
-
|
158
|
-
Args:
|
159
|
-
event: The ADK event containing the artifact information.
|
160
|
-
invocation_context: The invocation context.
|
161
|
-
filename: The name of the artifact file.
|
162
|
-
version: The version number of the artifact.
|
163
|
-
task_id: Optional task ID to use for generated events. If not provided, new UUIDs will be generated.
|
164
|
-
|
165
|
-
Returns:
|
166
|
-
A TaskArtifactUpdateEvent representing the artifact update.
|
167
|
-
|
168
|
-
Raises:
|
169
|
-
ValueError: If required parameters are invalid.
|
170
|
-
RuntimeError: If artifact loading fails.
|
171
|
-
"""
|
172
|
-
if not filename:
|
173
|
-
raise ValueError("Filename cannot be empty")
|
174
|
-
if version < 0:
|
175
|
-
raise ValueError("Version must be non-negative")
|
176
|
-
|
177
|
-
try:
|
178
|
-
artifact_part = invocation_context.artifact_service.load_artifact(
|
179
|
-
app_name=invocation_context.app_name,
|
180
|
-
user_id=invocation_context.user_id,
|
181
|
-
session_id=invocation_context.session.id,
|
182
|
-
filename=filename,
|
183
|
-
version=version,
|
184
|
-
)
|
185
|
-
|
186
|
-
converted_part = convert_genai_part_to_a2a_part(part=artifact_part)
|
187
|
-
if not converted_part:
|
188
|
-
raise RuntimeError(f"Failed to convert artifact part for {filename}")
|
189
|
-
|
190
|
-
artifact_id = _create_artifact_id(
|
191
|
-
invocation_context.app_name,
|
192
|
-
invocation_context.user_id,
|
193
|
-
invocation_context.session.id,
|
194
|
-
filename,
|
195
|
-
version,
|
196
|
-
)
|
197
|
-
|
198
|
-
return TaskArtifactUpdateEvent(
|
199
|
-
taskId=task_id,
|
200
|
-
append=False,
|
201
|
-
contextId=context_id,
|
202
|
-
lastChunk=True,
|
203
|
-
artifact=Artifact(
|
204
|
-
artifactId=artifact_id,
|
205
|
-
name=filename,
|
206
|
-
metadata={
|
207
|
-
"filename": filename,
|
208
|
-
"version": version,
|
209
|
-
},
|
210
|
-
parts=[converted_part],
|
211
|
-
),
|
212
|
-
)
|
213
|
-
except Exception as e:
|
214
|
-
logger.error(
|
215
|
-
"Failed to convert artifact for %s, version %s: %s",
|
216
|
-
filename,
|
217
|
-
version,
|
218
|
-
e,
|
219
|
-
)
|
220
|
-
raise RuntimeError(f"Artifact conversion failed: {e}") from e
|
221
|
-
|
222
|
-
|
223
146
|
def _process_long_running_tool(a2a_part: A2APart, event: Event) -> None:
|
224
147
|
"""Processes long-running tool metadata for an A2A part.
|
225
148
|
|
@@ -268,7 +191,11 @@ def convert_a2a_task_to_event(
|
|
268
191
|
try:
|
269
192
|
# Extract message from task status or history
|
270
193
|
message = None
|
271
|
-
if a2a_task.
|
194
|
+
if a2a_task.artifacts:
|
195
|
+
message = Message(
|
196
|
+
messageId="", role=Role.agent, parts=a2a_task.artifacts[-1].parts
|
197
|
+
)
|
198
|
+
elif a2a_task.status and a2a_task.status.message:
|
272
199
|
message = a2a_task.status.message
|
273
200
|
elif a2a_task.history:
|
274
201
|
message = a2a_task.history[-1]
|
@@ -573,13 +500,6 @@ def convert_event_to_a2a_events(
|
|
573
500
|
a2a_events = []
|
574
501
|
|
575
502
|
try:
|
576
|
-
# Handle artifact deltas
|
577
|
-
if event.actions.artifact_delta:
|
578
|
-
for filename, version in event.actions.artifact_delta.items():
|
579
|
-
artifact_event = _convert_artifact_to_a2a_events(
|
580
|
-
event, invocation_context, filename, version, task_id, context_id
|
581
|
-
)
|
582
|
-
a2a_events.append(artifact_event)
|
583
503
|
|
584
504
|
# Handle error scenarios
|
585
505
|
if event.error_code:
|
@@ -28,8 +28,10 @@ try:
|
|
28
28
|
from a2a.server.agent_execution import AgentExecutor
|
29
29
|
from a2a.server.agent_execution.context import RequestContext
|
30
30
|
from a2a.server.events.event_queue import EventQueue
|
31
|
+
from a2a.types import Artifact
|
31
32
|
from a2a.types import Message
|
32
33
|
from a2a.types import Role
|
34
|
+
from a2a.types import TaskArtifactUpdateEvent
|
33
35
|
from a2a.types import TaskState
|
34
36
|
from a2a.types import TaskStatus
|
35
37
|
from a2a.types import TaskStatusUpdateEvent
|
@@ -218,22 +220,49 @@ class A2aAgentExecutor(AgentExecutor):
|
|
218
220
|
await event_queue.enqueue_event(a2a_event)
|
219
221
|
|
220
222
|
# publish the task result event - this is final
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
223
|
+
if (
|
224
|
+
task_result_aggregator.task_state == TaskState.working
|
225
|
+
and task_result_aggregator.task_status_message is not None
|
226
|
+
and task_result_aggregator.task_status_message.parts
|
227
|
+
):
|
228
|
+
# if task is still working properly, publish the artifact update event as
|
229
|
+
# the final result according to a2a protocol.
|
230
|
+
await event_queue.enqueue_event(
|
231
|
+
TaskArtifactUpdateEvent(
|
232
|
+
taskId=context.task_id,
|
233
|
+
lastChunk=True,
|
234
|
+
contextId=context.context_id,
|
235
|
+
artifact=Artifact(
|
236
|
+
artifactId=str(uuid.uuid4()),
|
237
|
+
parts=task_result_aggregator.task_status_message.parts,
|
238
|
+
),
|
239
|
+
)
|
240
|
+
)
|
241
|
+
# public the final status update event
|
242
|
+
await event_queue.enqueue_event(
|
243
|
+
TaskStatusUpdateEvent(
|
244
|
+
taskId=context.task_id,
|
245
|
+
status=TaskStatus(
|
246
|
+
state=TaskState.completed,
|
247
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
248
|
+
),
|
249
|
+
contextId=context.context_id,
|
250
|
+
final=True,
|
251
|
+
)
|
252
|
+
)
|
253
|
+
else:
|
254
|
+
await event_queue.enqueue_event(
|
255
|
+
TaskStatusUpdateEvent(
|
256
|
+
taskId=context.task_id,
|
257
|
+
status=TaskStatus(
|
258
|
+
state=task_result_aggregator.task_state,
|
259
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
260
|
+
message=task_result_aggregator.task_status_message,
|
261
|
+
),
|
262
|
+
contextId=context.context_id,
|
263
|
+
final=True,
|
264
|
+
)
|
265
|
+
)
|
237
266
|
|
238
267
|
async def _prepare_session(
|
239
268
|
self, context: RequestContext, run_args: dict[str, Any], runner: Runner
|
google/adk/agents/__init__.py
CHANGED
@@ -13,6 +13,7 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
from .base_agent import BaseAgent
|
16
|
+
from .invocation_context import InvocationContext
|
16
17
|
from .live_request_queue import LiveRequest
|
17
18
|
from .live_request_queue import LiveRequestQueue
|
18
19
|
from .llm_agent import Agent
|
@@ -29,4 +30,8 @@ __all__ = [
|
|
29
30
|
'LoopAgent',
|
30
31
|
'ParallelAgent',
|
31
32
|
'SequentialAgent',
|
33
|
+
'InvocationContext',
|
34
|
+
'LiveRequest',
|
35
|
+
'LiveRequestQueue',
|
36
|
+
'RunConfig',
|
32
37
|
]
|
@@ -0,0 +1,46 @@
|
|
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 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 __future__ import annotations
|
16
|
+
|
17
|
+
from typing import Union
|
18
|
+
|
19
|
+
from pydantic import RootModel
|
20
|
+
|
21
|
+
from ..utils.feature_decorator import working_in_progress
|
22
|
+
from .llm_agent import LlmAgentConfig
|
23
|
+
from .loop_agent import LoopAgentConfig
|
24
|
+
from .parallel_agent import ParallelAgentConfig
|
25
|
+
from .sequential_agent import SequentialAgentConfig
|
26
|
+
|
27
|
+
# A discriminated union of all possible agent configurations.
|
28
|
+
ConfigsUnion = Union[
|
29
|
+
LlmAgentConfig,
|
30
|
+
LoopAgentConfig,
|
31
|
+
ParallelAgentConfig,
|
32
|
+
SequentialAgentConfig,
|
33
|
+
]
|
34
|
+
|
35
|
+
|
36
|
+
# Use a RootModel to represent the agent directly at the top level.
|
37
|
+
# The `discriminator` is applied to the union within the RootModel.
|
38
|
+
@working_in_progress("AgentConfig is not ready for use.")
|
39
|
+
class AgentConfig(RootModel[ConfigsUnion]):
|
40
|
+
"""The config for the YAML schema to create an agent."""
|
41
|
+
|
42
|
+
class Config:
|
43
|
+
# Pydantic v2 requires this for discriminated unions on RootModel
|
44
|
+
# This tells the model to look at the 'agent_class' field of the input
|
45
|
+
# data to decide which model from the `ConfigsUnion` to use.
|
46
|
+
discriminator = "agent_class"
|
google/adk/agents/base_agent.py
CHANGED
@@ -19,9 +19,13 @@ from typing import Any
|
|
19
19
|
from typing import AsyncGenerator
|
20
20
|
from typing import Awaitable
|
21
21
|
from typing import Callable
|
22
|
+
from typing import Dict
|
22
23
|
from typing import final
|
24
|
+
from typing import List
|
25
|
+
from typing import Literal
|
23
26
|
from typing import Mapping
|
24
27
|
from typing import Optional
|
28
|
+
from typing import Type
|
25
29
|
from typing import TYPE_CHECKING
|
26
30
|
from typing import TypeVar
|
27
31
|
from typing import Union
|
@@ -32,11 +36,14 @@ from pydantic import BaseModel
|
|
32
36
|
from pydantic import ConfigDict
|
33
37
|
from pydantic import Field
|
34
38
|
from pydantic import field_validator
|
39
|
+
from pydantic import model_validator
|
35
40
|
from typing_extensions import override
|
36
41
|
from typing_extensions import TypeAlias
|
37
42
|
|
38
43
|
from ..events.event import Event
|
44
|
+
from ..utils.feature_decorator import working_in_progress
|
39
45
|
from .callback_context import CallbackContext
|
46
|
+
from .common_configs import CodeConfig
|
40
47
|
|
41
48
|
if TYPE_CHECKING:
|
42
49
|
from .invocation_context import InvocationContext
|
@@ -223,11 +230,18 @@ class BaseAgent(BaseModel):
|
|
223
230
|
"""
|
224
231
|
with tracer.start_as_current_span(f'agent_run [{self.name}]'):
|
225
232
|
ctx = self._create_invocation_context(parent_context)
|
226
|
-
|
233
|
+
|
234
|
+
if event := await self.__handle_before_agent_callback(ctx):
|
235
|
+
yield event
|
236
|
+
if ctx.end_invocation:
|
237
|
+
return
|
227
238
|
|
228
239
|
async for event in self._run_live_impl(ctx):
|
229
240
|
yield event
|
230
241
|
|
242
|
+
if event := await self.__handle_after_agent_callback(ctx):
|
243
|
+
yield event
|
244
|
+
|
231
245
|
async def _run_async_impl(
|
232
246
|
self, ctx: InvocationContext
|
233
247
|
) -> AsyncGenerator[Event, None]:
|
@@ -331,73 +345,99 @@ class BaseAgent(BaseModel):
|
|
331
345
|
) -> Optional[Event]:
|
332
346
|
"""Runs the before_agent_callback if it exists.
|
333
347
|
|
348
|
+
Args:
|
349
|
+
ctx: InvocationContext, the invocation context for this agent.
|
350
|
+
|
334
351
|
Returns:
|
335
352
|
Optional[Event]: an event if callback provides content or changed state.
|
336
353
|
"""
|
337
|
-
ret_event = None
|
338
|
-
|
339
|
-
if not self.canonical_before_agent_callbacks:
|
340
|
-
return ret_event
|
341
|
-
|
342
354
|
callback_context = CallbackContext(ctx)
|
343
355
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
if inspect.isawaitable(before_agent_callback_content):
|
349
|
-
before_agent_callback_content = await before_agent_callback_content
|
350
|
-
if before_agent_callback_content:
|
351
|
-
ret_event = Event(
|
352
|
-
invocation_id=ctx.invocation_id,
|
353
|
-
author=self.name,
|
354
|
-
branch=ctx.branch,
|
355
|
-
content=before_agent_callback_content,
|
356
|
-
actions=callback_context._event_actions,
|
356
|
+
# Run callbacks from the plugins.
|
357
|
+
before_agent_callback_content = (
|
358
|
+
await ctx.plugin_manager.run_before_agent_callback(
|
359
|
+
agent=self, callback_context=callback_context
|
357
360
|
)
|
358
|
-
|
359
|
-
return ret_event
|
361
|
+
)
|
360
362
|
|
361
|
-
|
363
|
+
# If no overrides are provided from the plugins, further run the canonical
|
364
|
+
# callbacks.
|
365
|
+
if (
|
366
|
+
not before_agent_callback_content
|
367
|
+
and self.canonical_before_agent_callbacks
|
368
|
+
):
|
369
|
+
for callback in self.canonical_before_agent_callbacks:
|
370
|
+
before_agent_callback_content = callback(
|
371
|
+
callback_context=callback_context
|
372
|
+
)
|
373
|
+
if inspect.isawaitable(before_agent_callback_content):
|
374
|
+
before_agent_callback_content = await before_agent_callback_content
|
375
|
+
if before_agent_callback_content:
|
376
|
+
break
|
377
|
+
|
378
|
+
# Process the override content if exists, and further process the state
|
379
|
+
# change if exists.
|
380
|
+
if before_agent_callback_content:
|
362
381
|
ret_event = Event(
|
382
|
+
invocation_id=ctx.invocation_id,
|
383
|
+
author=self.name,
|
384
|
+
branch=ctx.branch,
|
385
|
+
content=before_agent_callback_content,
|
386
|
+
actions=callback_context._event_actions,
|
387
|
+
)
|
388
|
+
ctx.end_invocation = True
|
389
|
+
return ret_event
|
390
|
+
|
391
|
+
if callback_context.state.has_delta():
|
392
|
+
return Event(
|
363
393
|
invocation_id=ctx.invocation_id,
|
364
394
|
author=self.name,
|
365
395
|
branch=ctx.branch,
|
366
396
|
actions=callback_context._event_actions,
|
367
397
|
)
|
368
398
|
|
369
|
-
return
|
399
|
+
return None
|
370
400
|
|
371
401
|
async def __handle_after_agent_callback(
|
372
402
|
self, invocation_context: InvocationContext
|
373
403
|
) -> Optional[Event]:
|
374
404
|
"""Runs the after_agent_callback if it exists.
|
375
405
|
|
406
|
+
Args:
|
407
|
+
invocation_context: InvocationContext, the invocation context for this
|
408
|
+
agent.
|
409
|
+
|
376
410
|
Returns:
|
377
411
|
Optional[Event]: an event if callback provides content or changed state.
|
378
412
|
"""
|
379
|
-
ret_event = None
|
380
|
-
|
381
|
-
if not self.canonical_after_agent_callbacks:
|
382
|
-
return ret_event
|
383
413
|
|
384
414
|
callback_context = CallbackContext(invocation_context)
|
385
415
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
if after_agent_callback_content:
|
391
|
-
ret_event = Event(
|
392
|
-
invocation_id=invocation_context.invocation_id,
|
393
|
-
author=self.name,
|
394
|
-
branch=invocation_context.branch,
|
395
|
-
content=after_agent_callback_content,
|
396
|
-
actions=callback_context._event_actions,
|
416
|
+
# Run callbacks from the plugins.
|
417
|
+
after_agent_callback_content = (
|
418
|
+
await invocation_context.plugin_manager.run_after_agent_callback(
|
419
|
+
agent=self, callback_context=callback_context
|
397
420
|
)
|
398
|
-
|
421
|
+
)
|
399
422
|
|
400
|
-
|
423
|
+
# If no overrides are provided from the plugins, further run the canonical
|
424
|
+
# callbacks.
|
425
|
+
if (
|
426
|
+
not after_agent_callback_content
|
427
|
+
and self.canonical_after_agent_callbacks
|
428
|
+
):
|
429
|
+
for callback in self.canonical_after_agent_callbacks:
|
430
|
+
after_agent_callback_content = callback(
|
431
|
+
callback_context=callback_context
|
432
|
+
)
|
433
|
+
if inspect.isawaitable(after_agent_callback_content):
|
434
|
+
after_agent_callback_content = await after_agent_callback_content
|
435
|
+
if after_agent_callback_content:
|
436
|
+
break
|
437
|
+
|
438
|
+
# Process the override content if exists, and further process the state
|
439
|
+
# change if exists.
|
440
|
+
if after_agent_callback_content:
|
401
441
|
ret_event = Event(
|
402
442
|
invocation_id=invocation_context.invocation_id,
|
403
443
|
author=self.name,
|
@@ -405,8 +445,17 @@ class BaseAgent(BaseModel):
|
|
405
445
|
content=after_agent_callback_content,
|
406
446
|
actions=callback_context._event_actions,
|
407
447
|
)
|
448
|
+
return ret_event
|
408
449
|
|
409
|
-
|
450
|
+
if callback_context.state.has_delta():
|
451
|
+
return Event(
|
452
|
+
invocation_id=invocation_context.invocation_id,
|
453
|
+
author=self.name,
|
454
|
+
branch=invocation_context.branch,
|
455
|
+
content=after_agent_callback_content,
|
456
|
+
actions=callback_context._event_actions,
|
457
|
+
)
|
458
|
+
return None
|
410
459
|
|
411
460
|
@override
|
412
461
|
def model_post_init(self, __context: Any) -> None:
|
@@ -439,3 +488,147 @@ class BaseAgent(BaseModel):
|
|
439
488
|
)
|
440
489
|
sub_agent.parent_agent = self
|
441
490
|
return self
|
491
|
+
|
492
|
+
@classmethod
|
493
|
+
@working_in_progress('BaseAgent.from_config is not ready for use.')
|
494
|
+
def from_config(
|
495
|
+
cls: Type[SelfAgent],
|
496
|
+
config: BaseAgentConfig,
|
497
|
+
config_abs_path: str,
|
498
|
+
) -> SelfAgent:
|
499
|
+
"""Creates an agent from a config.
|
500
|
+
|
501
|
+
This method converts fields in a config to the corresponding
|
502
|
+
fields in an agent.
|
503
|
+
|
504
|
+
Child classes should re-implement this method to support loading from their
|
505
|
+
custom config types.
|
506
|
+
|
507
|
+
Args:
|
508
|
+
config: The config to create the agent from.
|
509
|
+
|
510
|
+
Returns:
|
511
|
+
The created agent.
|
512
|
+
"""
|
513
|
+
from .config_agent_utils import build_sub_agent
|
514
|
+
from .config_agent_utils import resolve_callbacks
|
515
|
+
|
516
|
+
kwargs: Dict[str, Any] = {
|
517
|
+
'name': config.name,
|
518
|
+
'description': config.description,
|
519
|
+
}
|
520
|
+
if config.sub_agents:
|
521
|
+
sub_agents = []
|
522
|
+
for sub_agent_config in config.sub_agents:
|
523
|
+
sub_agent = build_sub_agent(
|
524
|
+
sub_agent_config, config_abs_path.rsplit('/', 1)[0]
|
525
|
+
)
|
526
|
+
sub_agents.append(sub_agent)
|
527
|
+
kwargs['sub_agents'] = sub_agents
|
528
|
+
|
529
|
+
if config.before_agent_callbacks:
|
530
|
+
kwargs['before_agent_callback'] = resolve_callbacks(
|
531
|
+
config.before_agent_callbacks
|
532
|
+
)
|
533
|
+
if config.after_agent_callbacks:
|
534
|
+
kwargs['after_agent_callback'] = resolve_callbacks(
|
535
|
+
config.after_agent_callbacks
|
536
|
+
)
|
537
|
+
return cls(**kwargs)
|
538
|
+
|
539
|
+
|
540
|
+
class SubAgentConfig(BaseModel):
|
541
|
+
"""The config for a sub-agent."""
|
542
|
+
|
543
|
+
model_config = ConfigDict(extra='forbid')
|
544
|
+
|
545
|
+
config: Optional[str] = None
|
546
|
+
"""The YAML config file path of the sub-agent.
|
547
|
+
|
548
|
+
Only one of `config` or `code` can be set.
|
549
|
+
|
550
|
+
Example:
|
551
|
+
|
552
|
+
```
|
553
|
+
sub_agents:
|
554
|
+
- config: search_agent.yaml
|
555
|
+
- config: my_library/my_custom_agent.yaml
|
556
|
+
```
|
557
|
+
"""
|
558
|
+
|
559
|
+
code: Optional[str] = None
|
560
|
+
"""The agent instance defined in the code.
|
561
|
+
|
562
|
+
Only one of `config` or `code` can be set.
|
563
|
+
|
564
|
+
Example:
|
565
|
+
|
566
|
+
For the following agent defined in Python code:
|
567
|
+
|
568
|
+
```
|
569
|
+
# my_library/custom_agents.py
|
570
|
+
from google.adk.agents import LlmAgent
|
571
|
+
|
572
|
+
my_custom_agent = LlmAgent(
|
573
|
+
name="my_custom_agent",
|
574
|
+
instruction="You are a helpful custom agent.",
|
575
|
+
model="gemini-2.0-flash",
|
576
|
+
)
|
577
|
+
```
|
578
|
+
|
579
|
+
The yaml config should be:
|
580
|
+
|
581
|
+
```
|
582
|
+
sub_agents:
|
583
|
+
- code: my_library.custom_agents.my_custom_agent
|
584
|
+
```
|
585
|
+
"""
|
586
|
+
|
587
|
+
@model_validator(mode='after')
|
588
|
+
def validate_exactly_one_field(self):
|
589
|
+
code_provided = self.code is not None
|
590
|
+
config_provided = self.config is not None
|
591
|
+
|
592
|
+
if code_provided and config_provided:
|
593
|
+
raise ValueError('Only one of code or config should be provided')
|
594
|
+
if not code_provided and not config_provided:
|
595
|
+
raise ValueError('Exactly one of code or config must be provided')
|
596
|
+
|
597
|
+
return self
|
598
|
+
|
599
|
+
|
600
|
+
@working_in_progress('BaseAgentConfig is not ready for use.')
|
601
|
+
class BaseAgentConfig(BaseModel):
|
602
|
+
"""The config for the YAML schema of a BaseAgent.
|
603
|
+
|
604
|
+
Do not use this class directly. It's the base class for all agent configs.
|
605
|
+
"""
|
606
|
+
|
607
|
+
model_config = ConfigDict(extra='forbid')
|
608
|
+
|
609
|
+
agent_class: Literal['BaseAgent'] = 'BaseAgent'
|
610
|
+
"""Required. The class of the agent. The value is used to differentiate
|
611
|
+
among different agent classes."""
|
612
|
+
|
613
|
+
name: str
|
614
|
+
"""Required. The name of the agent."""
|
615
|
+
|
616
|
+
description: str = ''
|
617
|
+
"""Optional. The description of the agent."""
|
618
|
+
|
619
|
+
sub_agents: Optional[List[SubAgentConfig]] = None
|
620
|
+
"""Optional. The sub-agents of the agent."""
|
621
|
+
|
622
|
+
before_agent_callbacks: Optional[List[CodeConfig]] = None
|
623
|
+
"""Optional. The before_agent_callbacks of the agent.
|
624
|
+
|
625
|
+
Example:
|
626
|
+
|
627
|
+
```
|
628
|
+
before_agent_callbacks:
|
629
|
+
- name: my_library.security_callbacks.before_agent_callback
|
630
|
+
```
|
631
|
+
"""
|
632
|
+
|
633
|
+
after_agent_callbacks: Optional[List[CodeConfig]] = None
|
634
|
+
"""Optional. The after_agent_callbacks of the agent."""
|
@@ -24,6 +24,8 @@ from .readonly_context import ReadonlyContext
|
|
24
24
|
if TYPE_CHECKING:
|
25
25
|
from google.genai import types
|
26
26
|
|
27
|
+
from ..auth.auth_credential import AuthCredential
|
28
|
+
from ..auth.auth_tool import AuthConfig
|
27
29
|
from ..events.event_actions import EventActions
|
28
30
|
from ..sessions.state import State
|
29
31
|
from .invocation_context import InvocationContext
|
@@ -105,3 +107,42 @@ class CallbackContext(ReadonlyContext):
|
|
105
107
|
)
|
106
108
|
self._event_actions.artifact_delta[filename] = version
|
107
109
|
return version
|
110
|
+
|
111
|
+
async def list_artifacts(self) -> list[str]:
|
112
|
+
"""Lists the filenames of the artifacts attached to the current session."""
|
113
|
+
if self._invocation_context.artifact_service is None:
|
114
|
+
raise ValueError("Artifact service is not initialized.")
|
115
|
+
return await self._invocation_context.artifact_service.list_artifact_keys(
|
116
|
+
app_name=self._invocation_context.app_name,
|
117
|
+
user_id=self._invocation_context.user_id,
|
118
|
+
session_id=self._invocation_context.session.id,
|
119
|
+
)
|
120
|
+
|
121
|
+
async def save_credential(self, auth_config: AuthConfig) -> None:
|
122
|
+
"""Saves a credential to the credential service.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
auth_config: The authentication configuration containing the credential.
|
126
|
+
"""
|
127
|
+
if self._invocation_context.credential_service is None:
|
128
|
+
raise ValueError("Credential service is not initialized.")
|
129
|
+
await self._invocation_context.credential_service.save_credential(
|
130
|
+
auth_config, self
|
131
|
+
)
|
132
|
+
|
133
|
+
async def load_credential(
|
134
|
+
self, auth_config: AuthConfig
|
135
|
+
) -> Optional[AuthCredential]:
|
136
|
+
"""Loads a credential from the credential service.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
auth_config: The authentication configuration for the credential.
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
The loaded credential, or None if not found.
|
143
|
+
"""
|
144
|
+
if self._invocation_context.credential_service is None:
|
145
|
+
raise ValueError("Credential service is not initialized.")
|
146
|
+
return await self._invocation_context.credential_service.load_credential(
|
147
|
+
auth_config, self
|
148
|
+
)
|