google-adk 0.5.0__py3-none-any.whl → 1.1.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/agents/base_agent.py +76 -30
- google/adk/agents/callback_context.py +2 -6
- google/adk/agents/llm_agent.py +122 -30
- google/adk/agents/loop_agent.py +1 -1
- google/adk/agents/parallel_agent.py +7 -0
- google/adk/agents/readonly_context.py +8 -0
- google/adk/agents/run_config.py +1 -1
- google/adk/agents/sequential_agent.py +31 -0
- google/adk/agents/transcription_entry.py +4 -2
- google/adk/artifacts/gcs_artifact_service.py +1 -1
- google/adk/artifacts/in_memory_artifact_service.py +1 -1
- google/adk/auth/auth_credential.py +10 -2
- google/adk/auth/auth_preprocessor.py +7 -1
- google/adk/auth/auth_tool.py +3 -4
- google/adk/cli/agent_graph.py +5 -5
- google/adk/cli/browser/index.html +4 -4
- google/adk/cli/browser/{main-ULN5R5I5.js → main-PKDNKWJE.js} +59 -60
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/cli.py +10 -9
- google/adk/cli/cli_deploy.py +7 -2
- google/adk/cli/cli_eval.py +109 -115
- google/adk/cli/cli_tools_click.py +179 -67
- google/adk/cli/fast_api.py +248 -197
- google/adk/cli/utils/agent_loader.py +137 -0
- google/adk/cli/utils/cleanup.py +40 -0
- google/adk/cli/utils/common.py +23 -0
- google/adk/cli/utils/evals.py +83 -0
- google/adk/cli/utils/logs.py +8 -5
- google/adk/code_executors/__init__.py +3 -1
- google/adk/code_executors/built_in_code_executor.py +52 -0
- google/adk/code_executors/code_execution_utils.py +2 -1
- google/adk/code_executors/container_code_executor.py +0 -1
- google/adk/code_executors/vertex_ai_code_executor.py +6 -8
- google/adk/evaluation/__init__.py +1 -1
- google/adk/evaluation/agent_evaluator.py +168 -128
- google/adk/evaluation/eval_case.py +104 -0
- google/adk/evaluation/eval_metrics.py +74 -0
- google/adk/evaluation/eval_result.py +86 -0
- google/adk/evaluation/eval_set.py +39 -0
- google/adk/evaluation/eval_set_results_manager.py +47 -0
- google/adk/evaluation/eval_sets_manager.py +43 -0
- google/adk/evaluation/evaluation_generator.py +88 -113
- google/adk/evaluation/evaluator.py +58 -0
- google/adk/evaluation/local_eval_set_results_manager.py +113 -0
- google/adk/evaluation/local_eval_sets_manager.py +264 -0
- google/adk/evaluation/response_evaluator.py +106 -1
- google/adk/evaluation/trajectory_evaluator.py +84 -2
- google/adk/events/event.py +6 -1
- google/adk/events/event_actions.py +6 -1
- google/adk/examples/base_example_provider.py +1 -0
- google/adk/examples/example_util.py +3 -2
- google/adk/flows/llm_flows/_code_execution.py +9 -1
- google/adk/flows/llm_flows/audio_transcriber.py +4 -3
- google/adk/flows/llm_flows/base_llm_flow.py +58 -21
- google/adk/flows/llm_flows/contents.py +3 -1
- google/adk/flows/llm_flows/functions.py +9 -8
- google/adk/flows/llm_flows/instructions.py +18 -80
- google/adk/flows/llm_flows/single_flow.py +2 -2
- google/adk/memory/__init__.py +1 -1
- google/adk/memory/_utils.py +23 -0
- google/adk/memory/base_memory_service.py +23 -21
- google/adk/memory/in_memory_memory_service.py +57 -25
- google/adk/memory/memory_entry.py +37 -0
- google/adk/memory/vertex_ai_rag_memory_service.py +38 -15
- google/adk/models/anthropic_llm.py +16 -9
- google/adk/models/base_llm.py +2 -1
- google/adk/models/base_llm_connection.py +2 -0
- google/adk/models/gemini_llm_connection.py +11 -11
- google/adk/models/google_llm.py +12 -2
- google/adk/models/lite_llm.py +80 -23
- google/adk/models/llm_response.py +16 -3
- google/adk/models/registry.py +1 -1
- google/adk/runners.py +98 -42
- google/adk/sessions/__init__.py +1 -1
- google/adk/sessions/_session_util.py +2 -1
- google/adk/sessions/base_session_service.py +6 -33
- google/adk/sessions/database_session_service.py +57 -67
- google/adk/sessions/in_memory_session_service.py +106 -24
- google/adk/sessions/session.py +3 -0
- google/adk/sessions/vertex_ai_session_service.py +44 -51
- google/adk/telemetry.py +7 -2
- google/adk/tools/__init__.py +4 -7
- google/adk/tools/_memory_entry_utils.py +30 -0
- google/adk/tools/agent_tool.py +10 -10
- google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
- google/adk/tools/apihub_tool/clients/apihub_client.py +10 -3
- google/adk/tools/apihub_tool/clients/secret_client.py +1 -0
- google/adk/tools/application_integration_tool/application_integration_toolset.py +111 -85
- google/adk/tools/application_integration_tool/clients/connections_client.py +28 -1
- google/adk/tools/application_integration_tool/clients/integration_client.py +7 -5
- google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
- google/adk/tools/base_toolset.py +96 -0
- google/adk/tools/bigquery/__init__.py +28 -0
- google/adk/tools/bigquery/bigquery_credentials.py +216 -0
- google/adk/tools/bigquery/bigquery_tool.py +116 -0
- google/adk/tools/{built_in_code_execution_tool.py → enterprise_search_tool.py} +17 -11
- google/adk/tools/function_parameter_parse_util.py +9 -2
- google/adk/tools/function_tool.py +33 -3
- google/adk/tools/get_user_choice_tool.py +1 -0
- google/adk/tools/google_api_tool/__init__.py +24 -70
- google/adk/tools/google_api_tool/google_api_tool.py +12 -6
- google/adk/tools/google_api_tool/{google_api_tool_set.py → google_api_toolset.py} +57 -55
- google/adk/tools/google_api_tool/google_api_toolsets.py +108 -0
- google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
- google/adk/tools/google_search_tool.py +2 -2
- google/adk/tools/langchain_tool.py +96 -49
- google/adk/tools/load_memory_tool.py +14 -5
- google/adk/tools/mcp_tool/__init__.py +3 -2
- google/adk/tools/mcp_tool/conversion_utils.py +6 -2
- google/adk/tools/mcp_tool/mcp_session_manager.py +80 -69
- google/adk/tools/mcp_tool/mcp_tool.py +35 -32
- google/adk/tools/mcp_tool/mcp_toolset.py +99 -194
- google/adk/tools/openapi_tool/auth/credential_exchangers/base_credential_exchanger.py +1 -3
- google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py +6 -7
- google/adk/tools/openapi_tool/common/common.py +5 -1
- google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +7 -2
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +27 -7
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +36 -32
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +11 -1
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +1 -1
- google/adk/tools/preload_memory_tool.py +27 -18
- google/adk/tools/retrieval/__init__.py +1 -1
- google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +1 -1
- google/adk/tools/toolbox_toolset.py +107 -0
- google/adk/tools/transfer_to_agent_tool.py +0 -1
- google/adk/utils/__init__.py +13 -0
- google/adk/utils/instructions_utils.py +131 -0
- google/adk/version.py +1 -1
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/METADATA +18 -19
- google_adk-1.1.0.dist-info/RECORD +200 -0
- google/adk/agents/remote_agent.py +0 -50
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -18
- google/adk/cli/fast_api.py.orig +0 -728
- google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
- google/adk/tools/toolbox_tool.py +0 -46
- google_adk-0.5.0.dist-info/RECORD +0 -180
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/WHEEL +0 -0
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/entry_points.txt +0 -0
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,137 @@
|
|
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
|
+
import importlib
|
18
|
+
import logging
|
19
|
+
import sys
|
20
|
+
|
21
|
+
from . import envs
|
22
|
+
from ...agents.base_agent import BaseAgent
|
23
|
+
|
24
|
+
logger = logging.getLogger("google_adk." + __name__)
|
25
|
+
|
26
|
+
|
27
|
+
class AgentLoader:
|
28
|
+
"""Centralized agent loading with proper isolation, caching, and .env loading.
|
29
|
+
Support loading agents from below folder/file structures:
|
30
|
+
a) agents_dir/agent_name.py (with root_agent or agent.root_agent in it)
|
31
|
+
b) agents_dir/agent_name_folder/__init__.py (with root_agent or agent.root_agent in the package)
|
32
|
+
c) agents_dir/agent_name_folder/agent.py (where agent.py has root_agent)
|
33
|
+
"""
|
34
|
+
|
35
|
+
def __init__(self, agents_dir: str):
|
36
|
+
self.agents_dir = agents_dir.rstrip("/")
|
37
|
+
self._original_sys_path = None
|
38
|
+
self._agent_cache: dict[str, BaseAgent] = {}
|
39
|
+
|
40
|
+
def _load_from_module_or_package(self, agent_name: str) -> BaseAgent:
|
41
|
+
# Load for case: Import "<agent_name>" (as a package or module)
|
42
|
+
# Covers structures:
|
43
|
+
# a) agents_dir/agent_name.py (with root_agent or agent.root_agent in it)
|
44
|
+
# b) agents_dir/agent_name_folder/__init__.py (with root_agent or agent.root_agent in the package)
|
45
|
+
try:
|
46
|
+
module_candidate = importlib.import_module(agent_name)
|
47
|
+
# Check for "root_agent" directly in "<agent_name>" module/package
|
48
|
+
if hasattr(module_candidate, "root_agent"):
|
49
|
+
logger.debug("Found root_agent directly in %s", agent_name)
|
50
|
+
return module_candidate.root_agent
|
51
|
+
# Check for "<agent_name>.agent.root_agent" structure (e.g. agent_name is a package,
|
52
|
+
# and it has an 'agent' submodule/attribute which in turn has 'root_agent')
|
53
|
+
if hasattr(module_candidate, "agent") and hasattr(
|
54
|
+
module_candidate.agent, "root_agent"
|
55
|
+
):
|
56
|
+
logger.debug("Found root_agent in %s.agent attribute", agent_name)
|
57
|
+
if isinstance(module_candidate.agent, BaseAgent):
|
58
|
+
return module_candidate.agent.root_agent
|
59
|
+
else:
|
60
|
+
logger.warning(
|
61
|
+
"Root agent found is not an instance of BaseAgent. But a type %s",
|
62
|
+
type(module_candidate.agent),
|
63
|
+
)
|
64
|
+
except ModuleNotFoundError:
|
65
|
+
logger.debug("Module %s itself not found.", agent_name)
|
66
|
+
# Re-raise as ValueError to be caught by the final error message construction
|
67
|
+
raise ValueError(
|
68
|
+
f"Module {agent_name} not found during import attempts."
|
69
|
+
) from None
|
70
|
+
except ImportError as e:
|
71
|
+
logger.warning("Error importing %s: %s", agent_name, e)
|
72
|
+
|
73
|
+
return None
|
74
|
+
|
75
|
+
def _load_from_submodule(self, agent_name: str) -> BaseAgent:
|
76
|
+
# Load for case: Import "<agent_name>.agent" and look for "root_agent"
|
77
|
+
# Covers structure: agents_dir/agent_name_folder/agent.py (where agent.py has root_agent)
|
78
|
+
try:
|
79
|
+
module_candidate = importlib.import_module(f"{agent_name}.agent")
|
80
|
+
if hasattr(module_candidate, "root_agent"):
|
81
|
+
logger.debug("Found root_agent in %s.agent", agent_name)
|
82
|
+
if isinstance(module_candidate.root_agent, BaseAgent):
|
83
|
+
return module_candidate.root_agent
|
84
|
+
else:
|
85
|
+
logger.warning(
|
86
|
+
"Root agent found is not an instance of BaseAgent. But a type %s",
|
87
|
+
type(module_candidate.root_agent),
|
88
|
+
)
|
89
|
+
except ModuleNotFoundError:
|
90
|
+
logger.debug(
|
91
|
+
"Module %s.agent not found, trying next pattern.", agent_name
|
92
|
+
)
|
93
|
+
except ImportError as e:
|
94
|
+
logger.warning("Error importing %s.agent: %s", agent_name, e)
|
95
|
+
|
96
|
+
return None
|
97
|
+
|
98
|
+
def _perform_load(self, agent_name: str) -> BaseAgent:
|
99
|
+
"""Internal logic to load an agent"""
|
100
|
+
# Add self.agents_dir to sys.path
|
101
|
+
if self.agents_dir not in sys.path:
|
102
|
+
sys.path.insert(0, self.agents_dir)
|
103
|
+
|
104
|
+
logger.debug(
|
105
|
+
"Loading .env for agent %s from %s", agent_name, self.agents_dir
|
106
|
+
)
|
107
|
+
envs.load_dotenv_for_agent(agent_name, str(self.agents_dir))
|
108
|
+
|
109
|
+
root_agent = self._load_from_module_or_package(agent_name)
|
110
|
+
if root_agent:
|
111
|
+
return root_agent
|
112
|
+
|
113
|
+
root_agent = self._load_from_submodule(agent_name)
|
114
|
+
if root_agent:
|
115
|
+
return root_agent
|
116
|
+
|
117
|
+
# If no root_agent was found by any pattern
|
118
|
+
raise ValueError(
|
119
|
+
f"No root_agent found for '{agent_name}'. Searched in"
|
120
|
+
f" '{agent_name}.agent.root_agent', '{agent_name}.root_agent', and"
|
121
|
+
f" via an 'agent' attribute within the '{agent_name}' module/package."
|
122
|
+
f" Ensure '{self.agents_dir}/{agent_name}' is structured correctly,"
|
123
|
+
" an .env file can be loaded if present, and a root_agent is"
|
124
|
+
" exposed."
|
125
|
+
)
|
126
|
+
|
127
|
+
def load_agent(self, agent_name: str) -> BaseAgent:
|
128
|
+
"""Load an agent module (with caching & .env) and return its root_agent (asynchronously)."""
|
129
|
+
if agent_name in self._agent_cache:
|
130
|
+
logger.debug("Returning cached agent for %s (async)", agent_name)
|
131
|
+
return self._agent_cache[agent_name]
|
132
|
+
|
133
|
+
logger.debug("Loading agent %s - not in cache.", agent_name)
|
134
|
+
# Assumes this method is called when the context manager (`with self:`) is active
|
135
|
+
agent = self._perform_load(agent_name)
|
136
|
+
self._agent_cache[agent_name] = agent
|
137
|
+
return agent
|
@@ -0,0 +1,40 @@
|
|
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
|
+
import asyncio
|
16
|
+
import logging
|
17
|
+
from typing import List
|
18
|
+
|
19
|
+
from ...runners import Runner
|
20
|
+
|
21
|
+
logger = logging.getLogger("google_adk." + __name__)
|
22
|
+
|
23
|
+
|
24
|
+
async def close_runners(runners: List[Runner]) -> None:
|
25
|
+
cleanup_tasks = [asyncio.create_task(runner.close()) for runner in runners]
|
26
|
+
if cleanup_tasks:
|
27
|
+
# Wait for all cleanup tasks with timeout
|
28
|
+
done, pending = await asyncio.wait(
|
29
|
+
cleanup_tasks,
|
30
|
+
timeout=30.0, # 30 second timeout for cleanup
|
31
|
+
return_when=asyncio.ALL_COMPLETED,
|
32
|
+
)
|
33
|
+
|
34
|
+
# If any tasks are still pending, log it
|
35
|
+
if pending:
|
36
|
+
logger.warning(
|
37
|
+
"%s runner close tasks didn't complete in time", len(pending)
|
38
|
+
)
|
39
|
+
for task in pending:
|
40
|
+
task.cancel()
|
@@ -0,0 +1,23 @@
|
|
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
|
+
import pydantic
|
16
|
+
from pydantic import alias_generators
|
17
|
+
|
18
|
+
|
19
|
+
class BaseModel(pydantic.BaseModel):
|
20
|
+
model_config = pydantic.ConfigDict(
|
21
|
+
alias_generator=alias_generators.to_camel,
|
22
|
+
populate_by_name=True,
|
23
|
+
)
|
google/adk/cli/utils/evals.py
CHANGED
@@ -13,10 +13,17 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
from typing import Any
|
16
|
+
from typing import Tuple
|
16
17
|
|
18
|
+
from deprecated import deprecated
|
19
|
+
from google.genai import types as genai_types
|
20
|
+
|
21
|
+
from ...evaluation.eval_case import IntermediateData
|
22
|
+
from ...evaluation.eval_case import Invocation
|
17
23
|
from ...sessions.session import Session
|
18
24
|
|
19
25
|
|
26
|
+
@deprecated(reason='Use convert_session_to_eval_invocations instead.')
|
20
27
|
def convert_session_to_eval_format(session: Session) -> list[dict[str, Any]]:
|
21
28
|
"""Converts a session data into eval format.
|
22
29
|
|
@@ -91,3 +98,79 @@ def convert_session_to_eval_format(session: Session) -> list[dict[str, Any]]:
|
|
91
98
|
})
|
92
99
|
|
93
100
|
return eval_case
|
101
|
+
|
102
|
+
|
103
|
+
def convert_session_to_eval_invocations(session: Session) -> list[Invocation]:
|
104
|
+
"""Converts a session data into a list of Invocation.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
session: The session that should be converted.
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
list: A list of invocation.
|
111
|
+
"""
|
112
|
+
invocations: list[Invocation] = []
|
113
|
+
events = session.events if session and session.events else []
|
114
|
+
|
115
|
+
for event in events:
|
116
|
+
if event.author == 'user':
|
117
|
+
if not event.content or not event.content.parts:
|
118
|
+
continue
|
119
|
+
|
120
|
+
# The content present in this event is the user content.
|
121
|
+
user_content = event.content
|
122
|
+
invocation_id = event.invocation_id
|
123
|
+
invocaton_timestamp = event.timestamp
|
124
|
+
|
125
|
+
# Find the corresponding tool usage or response for the query
|
126
|
+
tool_uses: list[genai_types.FunctionCall] = []
|
127
|
+
intermediate_responses: list[Tuple[str, list[genai_types.Part]]] = []
|
128
|
+
|
129
|
+
# Check subsequent events to extract tool uses or responses for this turn.
|
130
|
+
for subsequent_event in events[events.index(event) + 1 :]:
|
131
|
+
event_author = subsequent_event.author or 'agent'
|
132
|
+
if event_author == 'user':
|
133
|
+
# We found an event where the author was the user. This means that a
|
134
|
+
# new turn has started. So close this turn here.
|
135
|
+
break
|
136
|
+
|
137
|
+
if not subsequent_event.content or not subsequent_event.content.parts:
|
138
|
+
continue
|
139
|
+
|
140
|
+
intermediate_response_parts = []
|
141
|
+
for subsequent_part in subsequent_event.content.parts:
|
142
|
+
# Some events have both function call and reference
|
143
|
+
if subsequent_part.function_call:
|
144
|
+
tool_uses.append(subsequent_part.function_call)
|
145
|
+
elif subsequent_part.text:
|
146
|
+
# Also keep track of all the natural language responses that
|
147
|
+
# agent (or sub agents) generated.
|
148
|
+
intermediate_response_parts.append(subsequent_part)
|
149
|
+
|
150
|
+
if intermediate_response_parts:
|
151
|
+
# Only add an entry if there any intermediate entries.
|
152
|
+
intermediate_responses.append(
|
153
|
+
(event_author, intermediate_response_parts)
|
154
|
+
)
|
155
|
+
|
156
|
+
# If we are here then either we are done reading all the events or we
|
157
|
+
# encountered an event that had content authored by the end-user.
|
158
|
+
# This, basically means an end of turn.
|
159
|
+
# We assume that the last natural language intermediate response is the
|
160
|
+
# final response from the agent/model. We treat that as a reference.
|
161
|
+
invocations.append(
|
162
|
+
Invocation(
|
163
|
+
user_content=user_content,
|
164
|
+
invocation_id=invocation_id,
|
165
|
+
creation_timestamp=invocaton_timestamp,
|
166
|
+
intermediate_data=IntermediateData(
|
167
|
+
tool_uses=tool_uses,
|
168
|
+
intermediate_responses=intermediate_responses[:-1],
|
169
|
+
),
|
170
|
+
final_response=genai_types.Content(
|
171
|
+
parts=intermediate_responses[-1][1]
|
172
|
+
),
|
173
|
+
)
|
174
|
+
)
|
175
|
+
|
176
|
+
return invocations
|
google/adk/cli/utils/logs.py
CHANGED
@@ -12,6 +12,8 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import logging
|
16
18
|
import os
|
17
19
|
import tempfile
|
@@ -22,11 +24,12 @@ LOGGING_FORMAT = (
|
|
22
24
|
)
|
23
25
|
|
24
26
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
)
|
27
|
+
def setup_adk_logger(level=logging.INFO):
|
28
|
+
# Configure the root logger format and level.
|
29
|
+
logging.basicConfig(level=level, format=LOGGING_FORMAT)
|
30
|
+
|
31
|
+
adk_logger = logging.getLogger('google_adk')
|
32
|
+
adk_logger.setLevel(level)
|
30
33
|
|
31
34
|
|
32
35
|
def log_to_tmp_folder(
|
@@ -15,13 +15,15 @@
|
|
15
15
|
import logging
|
16
16
|
|
17
17
|
from .base_code_executor import BaseCodeExecutor
|
18
|
+
from .built_in_code_executor import BuiltInCodeExecutor
|
18
19
|
from .code_executor_context import CodeExecutorContext
|
19
20
|
from .unsafe_local_code_executor import UnsafeLocalCodeExecutor
|
20
21
|
|
21
|
-
logger = logging.getLogger(__name__)
|
22
|
+
logger = logging.getLogger('google_adk.' + __name__)
|
22
23
|
|
23
24
|
__all__ = [
|
24
25
|
'BaseCodeExecutor',
|
26
|
+
'BuiltInCodeExecutor',
|
25
27
|
'CodeExecutorContext',
|
26
28
|
'UnsafeLocalCodeExecutor',
|
27
29
|
]
|
@@ -0,0 +1,52 @@
|
|
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 google.genai import types
|
16
|
+
from typing_extensions import override
|
17
|
+
|
18
|
+
from ..agents.invocation_context import InvocationContext
|
19
|
+
from ..models import LlmRequest
|
20
|
+
from .base_code_executor import BaseCodeExecutor
|
21
|
+
from .code_execution_utils import CodeExecutionInput
|
22
|
+
from .code_execution_utils import CodeExecutionResult
|
23
|
+
|
24
|
+
|
25
|
+
class BuiltInCodeExecutor(BaseCodeExecutor):
|
26
|
+
"""A code executor that uses the Model's built-in code executor.
|
27
|
+
|
28
|
+
Currently only supports Gemini 2.0+ models, but will be expanded to
|
29
|
+
other models.
|
30
|
+
"""
|
31
|
+
|
32
|
+
@override
|
33
|
+
def execute_code(
|
34
|
+
self,
|
35
|
+
invocation_context: InvocationContext,
|
36
|
+
code_execution_input: CodeExecutionInput,
|
37
|
+
) -> CodeExecutionResult:
|
38
|
+
pass
|
39
|
+
|
40
|
+
def process_llm_request(self, llm_request: LlmRequest) -> None:
|
41
|
+
"""Pre-process the LLM request for Gemini 2.0+ models to use the code execution tool."""
|
42
|
+
if llm_request.model and llm_request.model.startswith("gemini-2"):
|
43
|
+
llm_request.config = llm_request.config or types.GenerateContentConfig()
|
44
|
+
llm_request.config.tools = llm_request.config.tools or []
|
45
|
+
llm_request.config.tools.append(
|
46
|
+
types.Tool(code_execution=types.ToolCodeExecution())
|
47
|
+
)
|
48
|
+
return
|
49
|
+
raise ValueError(
|
50
|
+
"Gemini code execution tool is not supported for model"
|
51
|
+
f" {llm_request.model}"
|
52
|
+
)
|
@@ -15,7 +15,8 @@
|
|
15
15
|
import datetime
|
16
16
|
import mimetypes
|
17
17
|
import os
|
18
|
-
from typing import Any
|
18
|
+
from typing import Any
|
19
|
+
from typing import Optional
|
19
20
|
|
20
21
|
from typing_extensions import override
|
21
22
|
from vertexai.preview.extensions import Extension
|
@@ -147,18 +148,15 @@ class VertexAiCodeExecutor(BaseCodeExecutor):
|
|
147
148
|
)
|
148
149
|
|
149
150
|
# Save output file as artifacts.
|
150
|
-
current_timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
|
151
|
-
file_name_prefix = '%s_' % str(current_timestamp)
|
152
151
|
saved_files = []
|
153
152
|
file_count = 0
|
154
153
|
for output_file in code_execution_result['output_files']:
|
155
154
|
file_type = output_file['name'].split('.')[-1]
|
156
|
-
file_name = file_name_prefix + '%d.%s' % (file_count, file_type)
|
157
155
|
if file_type in _SUPPORTED_IMAGE_TYPES:
|
158
156
|
file_count += 1
|
159
157
|
saved_files.append(
|
160
158
|
File(
|
161
|
-
name='
|
159
|
+
name=output_file['name'],
|
162
160
|
content=output_file['contents'],
|
163
161
|
mime_type=f'image/{file_type}',
|
164
162
|
)
|
@@ -167,16 +165,16 @@ class VertexAiCodeExecutor(BaseCodeExecutor):
|
|
167
165
|
file_count += 1
|
168
166
|
saved_files.append(
|
169
167
|
File(
|
170
|
-
name='
|
168
|
+
name=output_file['name'],
|
171
169
|
content=output_file['contents'],
|
172
170
|
mime_type=f'text/{file_type}',
|
173
171
|
)
|
174
172
|
)
|
175
173
|
else:
|
176
|
-
mime_type, _ = mimetypes.guess_type(
|
174
|
+
mime_type, _ = mimetypes.guess_type(output_file['name'])
|
177
175
|
saved_files.append(
|
178
176
|
File(
|
179
|
-
name=
|
177
|
+
name=output_file['name'],
|
180
178
|
content=output_file['contents'],
|
181
179
|
mime_type=mime_type,
|
182
180
|
)
|