solace-agent-mesh 1.3.0__py3-none-any.whl → 1.3.1__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.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/setup.py +141 -34
- solace_agent_mesh/agent/sac/app.py +1 -2
- solace_agent_mesh/agent/tools/__init__.py +1 -0
- solace_agent_mesh/agent/tools/dynamic_tool.py +362 -0
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/42b3f8d8.d97b8e94.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/483cef9a.4e972867.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/55f47984.cf3781c4.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/664b740a.1b744a32.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/75384d09.c193a8f0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9a09e75d.d6607c56.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/aba87c2f.071e2d94.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ae0e903d.4d8dda10.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c835a94d.146e3186.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.7334119c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.1c79039d.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.858117b7.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +29 -0
- solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +25 -0
- solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html } +6 -6
- solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html } +6 -6
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +19 -27
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +5 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +63 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/lunr-index-1757531604543.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1757531604543.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-vY5eu2lI.js → authCallback-CAX9u8a7.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-BeBkzgWW.js → client-DXU9SPI5.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{main-Bjys1KQs.js → main-C1k9E0aC.js} +26 -26
- solace_agent_mesh/client/webui/frontend/static/assets/{vendor-CE0AeXyK.js → vendor-B0BEKoAR.js} +69 -74
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
- solace_agent_mesh/client/webui/frontend/static/index.html +3 -3
- solace_agent_mesh/gateway/http_sse/dependencies.py +6 -6
- solace_agent_mesh/gateway/http_sse/main.py +2 -2
- solace_agent_mesh/gateway/http_sse/routers/{agents.py → agent_cards.py} +7 -7
- solace_agent_mesh/gateway/http_sse/services/{agent_service.py → agent_card_service.py} +19 -19
- {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/METADATA +1 -1
- {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/RECORD +77 -70
- solace_agent_mesh/assets/docs/assets/js/42b3f8d8.8ccb9901.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/75384d09.bf78fbdb.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/aba87c2f.76376d7c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.08d30374.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1757433031159.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1757433031159.json +0 -1
- /solace_agent_mesh/assets/docs/assets/js/{main.08d30374.js.LICENSE.txt → main.1c79039d.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.3.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,7 +18,6 @@ from google.adk import tools as adk_tools_module
|
|
|
18
18
|
from google.adk.agents.callback_context import CallbackContext
|
|
19
19
|
from google.adk.models.llm_request import LlmRequest
|
|
20
20
|
from google.adk.models.llm_response import LlmResponse
|
|
21
|
-
from google.adk.tools.mcp_tool import MCPToolset
|
|
22
21
|
from google.adk.tools.mcp_tool.mcp_session_manager import (
|
|
23
22
|
SseServerParams,
|
|
24
23
|
StdioConnectionParams,
|
|
@@ -28,12 +27,49 @@ from mcp import StdioServerParameters
|
|
|
28
27
|
|
|
29
28
|
from ..tools.registry import tool_registry
|
|
30
29
|
from ..tools.tool_definition import BuiltinTool
|
|
30
|
+
from ..tools.dynamic_tool import DynamicTool, DynamicToolProvider
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
from ...agent.adk import callbacks as adk_callbacks
|
|
34
34
|
from ...agent.adk.models.lite_llm import LiteLlm
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def _find_dynamic_tool_class(module) -> Optional[type]:
|
|
38
|
+
"""Finds a single non-abstract DynamicTool subclass in a module."""
|
|
39
|
+
found_classes = []
|
|
40
|
+
for name, obj in inspect.getmembers(module, inspect.isclass):
|
|
41
|
+
if (
|
|
42
|
+
issubclass(obj, DynamicTool)
|
|
43
|
+
and obj is not DynamicTool
|
|
44
|
+
and not inspect.isabstract(obj)
|
|
45
|
+
):
|
|
46
|
+
found_classes.append(obj)
|
|
47
|
+
if len(found_classes) > 1:
|
|
48
|
+
raise TypeError(
|
|
49
|
+
f"Module '{module.__name__}' contains multiple DynamicTool subclasses. "
|
|
50
|
+
"Please specify which one to use with 'class_name' in the config."
|
|
51
|
+
)
|
|
52
|
+
return found_classes[0] if found_classes else None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _find_dynamic_tool_provider_class(module) -> Optional[type]:
|
|
56
|
+
"""Finds a single non-abstract DynamicToolProvider subclass in a module."""
|
|
57
|
+
found_classes = []
|
|
58
|
+
for name, obj in inspect.getmembers(module, inspect.isclass):
|
|
59
|
+
if (
|
|
60
|
+
issubclass(obj, DynamicToolProvider)
|
|
61
|
+
and obj is not DynamicToolProvider
|
|
62
|
+
and not inspect.isabstract(obj)
|
|
63
|
+
):
|
|
64
|
+
found_classes.append(obj)
|
|
65
|
+
if len(found_classes) > 1:
|
|
66
|
+
raise TypeError(
|
|
67
|
+
f"Module '{module.__name__}' contains multiple DynamicToolProvider subclasses. "
|
|
68
|
+
"Only one is permitted per module."
|
|
69
|
+
)
|
|
70
|
+
return found_classes[0] if found_classes else None
|
|
71
|
+
|
|
72
|
+
|
|
37
73
|
async def load_adk_tools(
|
|
38
74
|
component,
|
|
39
75
|
) -> Tuple[List[Union[BaseTool, Callable]], List[BuiltinTool]]:
|
|
@@ -90,48 +126,119 @@ async def load_adk_tools(
|
|
|
90
126
|
try:
|
|
91
127
|
if tool_type == "python":
|
|
92
128
|
module_name = tool_config.get("component_module")
|
|
93
|
-
function_name = tool_config.get("function_name")
|
|
94
|
-
tool_name = tool_config.get("tool_name")
|
|
95
|
-
tool_description = tool_config.get("tool_description")
|
|
96
129
|
base_path = tool_config.get("component_base_path")
|
|
97
|
-
if not module_name
|
|
130
|
+
if not module_name:
|
|
98
131
|
raise ValueError(
|
|
99
|
-
"'component_module'
|
|
132
|
+
"'component_module' is required for python tools."
|
|
100
133
|
)
|
|
101
|
-
|
|
102
134
|
module = import_module(module_name, base_path=base_path)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
135
|
+
|
|
136
|
+
# Case 1: Simple function-based tool
|
|
137
|
+
if "function_name" in tool_config:
|
|
138
|
+
function_name = tool_config.get("function_name")
|
|
139
|
+
tool_name = tool_config.get("tool_name")
|
|
140
|
+
tool_description = tool_config.get("tool_description")
|
|
141
|
+
|
|
142
|
+
func = getattr(module, function_name)
|
|
143
|
+
if not callable(func):
|
|
144
|
+
raise TypeError(
|
|
145
|
+
f"'{function_name}' in module '{module_name}' is not callable."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
specific_tool_config = tool_config.get("tool_config")
|
|
149
|
+
tool_callable = ADKToolWrapper(
|
|
150
|
+
func,
|
|
151
|
+
specific_tool_config,
|
|
152
|
+
function_name,
|
|
153
|
+
origin="python",
|
|
154
|
+
raw_string_args=tool_config.get("raw_string_args", []),
|
|
107
155
|
)
|
|
108
156
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
157
|
+
if tool_name:
|
|
158
|
+
function_name = tool_name
|
|
159
|
+
tool_callable.__name__ = tool_name
|
|
160
|
+
|
|
161
|
+
if tool_description:
|
|
162
|
+
tool_callable.__doc__ = tool_description
|
|
163
|
+
|
|
164
|
+
_check_and_register_tool_name(
|
|
165
|
+
function_name, f"python:{module_name}"
|
|
166
|
+
)
|
|
167
|
+
loaded_tools.append(tool_callable)
|
|
168
|
+
log.info(
|
|
169
|
+
"%s Loaded Python tool: %s from %s.",
|
|
170
|
+
component.log_identifier,
|
|
171
|
+
function_name,
|
|
172
|
+
module_name,
|
|
173
|
+
)
|
|
174
|
+
# Case 2: Advanced class-based dynamic tool or provider
|
|
175
|
+
else:
|
|
176
|
+
specific_tool_config = tool_config.get("tool_config")
|
|
177
|
+
dynamic_tools = []
|
|
117
178
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
179
|
+
# Determine the class to load
|
|
180
|
+
tool_class = None
|
|
181
|
+
class_name = tool_config.get("class_name")
|
|
182
|
+
if class_name:
|
|
183
|
+
tool_class = getattr(module, class_name)
|
|
184
|
+
else:
|
|
185
|
+
# Auto-discover: provider first, then single tool
|
|
186
|
+
tool_class = _find_dynamic_tool_provider_class(module)
|
|
187
|
+
if not tool_class:
|
|
188
|
+
tool_class = _find_dynamic_tool_class(module)
|
|
189
|
+
|
|
190
|
+
if not tool_class:
|
|
191
|
+
raise TypeError(
|
|
192
|
+
f"Module '{module_name}' does not contain a 'function_name' or 'class_name' to load, "
|
|
193
|
+
"and no DynamicTool or DynamicToolProvider subclass could be auto-discovered."
|
|
194
|
+
)
|
|
121
195
|
|
|
122
|
-
|
|
123
|
-
|
|
196
|
+
# Instantiate tools from the class
|
|
197
|
+
if issubclass(tool_class, DynamicToolProvider):
|
|
198
|
+
provider_instance = tool_class()
|
|
199
|
+
dynamic_tools = (
|
|
200
|
+
provider_instance.get_all_tools_for_framework(
|
|
201
|
+
tool_config=specific_tool_config
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
log.info(
|
|
205
|
+
"%s Loaded %d tools from DynamicToolProvider '%s' in %s",
|
|
206
|
+
component.log_identifier,
|
|
207
|
+
len(dynamic_tools),
|
|
208
|
+
tool_class.__name__,
|
|
209
|
+
module_name,
|
|
210
|
+
)
|
|
211
|
+
elif issubclass(tool_class, DynamicTool):
|
|
212
|
+
tool_instance = tool_class(tool_config=specific_tool_config)
|
|
213
|
+
dynamic_tools = [tool_instance]
|
|
214
|
+
else:
|
|
215
|
+
raise TypeError(
|
|
216
|
+
f"Class '{tool_class.__name__}' in module '{module_name}' is not a valid "
|
|
217
|
+
"DynamicTool or DynamicToolProvider subclass."
|
|
218
|
+
)
|
|
124
219
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
220
|
+
# Process all generated tools
|
|
221
|
+
for tool in dynamic_tools:
|
|
222
|
+
tool.origin = "dynamic"
|
|
223
|
+
declaration = tool._get_declaration()
|
|
224
|
+
if not declaration:
|
|
225
|
+
log.warning(
|
|
226
|
+
"Dynamic tool '%s' from module '%s' did not generate a valid declaration. Skipping.",
|
|
227
|
+
tool.__class__.__name__,
|
|
228
|
+
module_name,
|
|
229
|
+
)
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
_check_and_register_tool_name(
|
|
233
|
+
declaration.name, f"dynamic:{module_name}"
|
|
234
|
+
)
|
|
235
|
+
loaded_tools.append(tool)
|
|
236
|
+
log.info(
|
|
237
|
+
"%s Loaded dynamic tool: %s from %s",
|
|
238
|
+
component.log_identifier,
|
|
239
|
+
declaration.name,
|
|
240
|
+
module_name,
|
|
241
|
+
)
|
|
135
242
|
|
|
136
243
|
elif tool_type == "builtin":
|
|
137
244
|
tool_name = tool_config.get("tool_name")
|
|
@@ -14,7 +14,6 @@ patch_adk()
|
|
|
14
14
|
|
|
15
15
|
from typing import Any, Dict
|
|
16
16
|
from solace_ai_connector.flow.app import App
|
|
17
|
-
from solace_ai_connector.common.log import log
|
|
18
17
|
|
|
19
18
|
from ...common.a2a import (
|
|
20
19
|
get_agent_request_topic,
|
|
@@ -714,4 +713,4 @@ class SamAgentApp(App):
|
|
|
714
713
|
log.debug("Set broker_config.temporary_queue = True")
|
|
715
714
|
|
|
716
715
|
super().__init__(app_info, **kwargs)
|
|
717
|
-
log.debug("A2A_ADK_App initialization complete.")
|
|
716
|
+
log.debug("A2A_ADK_App initialization complete.")
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Defines the base classes and helpers for "dynamic" tools.
|
|
3
|
+
Dynamic tools allow for programmatic definition of tool names, descriptions,
|
|
4
|
+
and parameter schemas, offering more flexibility than standard Python tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import (
|
|
9
|
+
Optional,
|
|
10
|
+
List,
|
|
11
|
+
Callable,
|
|
12
|
+
Dict,
|
|
13
|
+
Any,
|
|
14
|
+
get_origin,
|
|
15
|
+
get_args,
|
|
16
|
+
Union,
|
|
17
|
+
Literal,
|
|
18
|
+
)
|
|
19
|
+
import inspect
|
|
20
|
+
|
|
21
|
+
from google.adk.tools import BaseTool, ToolContext
|
|
22
|
+
from google.genai import types as adk_types
|
|
23
|
+
from solace_ai_connector.common.log import log
|
|
24
|
+
|
|
25
|
+
from solace_agent_mesh.agent.utils.context_helpers import get_original_session_id
|
|
26
|
+
|
|
27
|
+
from ...common.utils.embeds import (
|
|
28
|
+
resolve_embeds_in_string,
|
|
29
|
+
evaluate_embed,
|
|
30
|
+
EARLY_EMBED_TYPES,
|
|
31
|
+
LATE_EMBED_TYPES,
|
|
32
|
+
EMBED_DELIMITER_OPEN,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# --- Base Class for Programmatic Tools ---
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DynamicTool(BaseTool, ABC):
|
|
40
|
+
"""
|
|
41
|
+
Base class for dynamic tools that can define their own function names,
|
|
42
|
+
descriptions, and parameter schemas programmatically.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, tool_config: Optional[dict] = None):
|
|
46
|
+
# Initialize with placeholder values, will be overridden by properties
|
|
47
|
+
super().__init__(
|
|
48
|
+
name="dynamic_tool_placeholder", description="dynamic_tool_placeholder"
|
|
49
|
+
)
|
|
50
|
+
self.tool_config = tool_config or {}
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def tool_name(self) -> str:
|
|
55
|
+
"""Return the function name that the LLM will call."""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def tool_description(self) -> str:
|
|
61
|
+
"""Return the description of what this tool does."""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def parameters_schema(self) -> adk_types.Schema:
|
|
67
|
+
"""Return the ADK Schema defining the tool's parameters."""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def raw_string_args(self) -> List[str]:
|
|
72
|
+
"""
|
|
73
|
+
Return a list of argument names that should not have embeds resolved.
|
|
74
|
+
Subclasses can override this property.
|
|
75
|
+
"""
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def resolution_type(self) -> Literal["early", "all"]:
|
|
80
|
+
"""
|
|
81
|
+
Determines which embeds to resolve. 'early' resolves simple embeds like
|
|
82
|
+
math and uuid. 'all' also resolves 'artifact_content'.
|
|
83
|
+
Defaults to 'early'.
|
|
84
|
+
"""
|
|
85
|
+
return "early"
|
|
86
|
+
|
|
87
|
+
def _get_declaration(self) -> Optional[Any]:
|
|
88
|
+
"""
|
|
89
|
+
Generate the FunctionDeclaration for this dynamic tool.
|
|
90
|
+
This follows the same pattern as PeerAgentTool and MCP tools.
|
|
91
|
+
"""
|
|
92
|
+
# Update the tool name to match what the module defines
|
|
93
|
+
self.name = self.tool_name
|
|
94
|
+
|
|
95
|
+
return adk_types.FunctionDeclaration(
|
|
96
|
+
name=self.tool_name,
|
|
97
|
+
description=self.tool_description,
|
|
98
|
+
parameters=self.parameters_schema,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
async def run_async(
|
|
102
|
+
self, *, args: Dict[str, Any], tool_context: ToolContext
|
|
103
|
+
) -> Dict[str, Any]:
|
|
104
|
+
"""
|
|
105
|
+
Asynchronously runs the tool with the given arguments.
|
|
106
|
+
This method resolves embeds in arguments and then delegates the call
|
|
107
|
+
to the abstract _run_async_impl.
|
|
108
|
+
"""
|
|
109
|
+
log_identifier = f"[DynamicTool:{self.tool_name}]"
|
|
110
|
+
resolved_kwargs = args.copy()
|
|
111
|
+
|
|
112
|
+
types_to_resolve = EARLY_EMBED_TYPES
|
|
113
|
+
if self.resolution_type == "all":
|
|
114
|
+
types_to_resolve = EARLY_EMBED_TYPES.union(LATE_EMBED_TYPES)
|
|
115
|
+
|
|
116
|
+
# Unlike ADKToolWrapper, DynamicTools receive all args in a single dict.
|
|
117
|
+
# We iterate through this dict to resolve embeds.
|
|
118
|
+
for key, value in args.items():
|
|
119
|
+
if key in self.raw_string_args and isinstance(value, str):
|
|
120
|
+
log.debug(
|
|
121
|
+
"%s Skipping embed resolution for raw string kwarg '%s'",
|
|
122
|
+
log_identifier,
|
|
123
|
+
key,
|
|
124
|
+
)
|
|
125
|
+
elif isinstance(value, str) and EMBED_DELIMITER_OPEN in value:
|
|
126
|
+
log.debug("%s Resolving embeds for kwarg '%s'", log_identifier, key)
|
|
127
|
+
# Create the resolution context
|
|
128
|
+
if hasattr(tool_context, "_invocation_context"):
|
|
129
|
+
# Use the invocation context if available
|
|
130
|
+
invocation_context = tool_context._invocation_context
|
|
131
|
+
else:
|
|
132
|
+
# Error if no invocation context is found
|
|
133
|
+
raise RuntimeError(
|
|
134
|
+
f"{log_identifier} No invocation context found in ToolContext. Cannot resolve embeds."
|
|
135
|
+
)
|
|
136
|
+
session_context = invocation_context.session
|
|
137
|
+
if not session_context:
|
|
138
|
+
raise RuntimeError(
|
|
139
|
+
f"{log_identifier} No session context found in invocation context. Cannot resolve embeds."
|
|
140
|
+
)
|
|
141
|
+
resolution_context = {
|
|
142
|
+
"artifact_service": invocation_context.artifact_service,
|
|
143
|
+
"session_context": {
|
|
144
|
+
"session_id": get_original_session_id(invocation_context),
|
|
145
|
+
"user_id": session_context.user_id,
|
|
146
|
+
"app_name": session_context.app_name,
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
resolved_value, _, _ = await resolve_embeds_in_string(
|
|
150
|
+
text=value,
|
|
151
|
+
context=resolution_context,
|
|
152
|
+
resolver_func=evaluate_embed,
|
|
153
|
+
types_to_resolve=types_to_resolve,
|
|
154
|
+
log_identifier=log_identifier,
|
|
155
|
+
config=self.tool_config,
|
|
156
|
+
)
|
|
157
|
+
resolved_kwargs[key] = resolved_value
|
|
158
|
+
|
|
159
|
+
return await self._run_async_impl(
|
|
160
|
+
args=resolved_kwargs, tool_context=tool_context, credential=None
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
@abstractmethod
|
|
164
|
+
async def _run_async_impl(
|
|
165
|
+
self, args: dict, tool_context: ToolContext, credential: Optional[str] = None
|
|
166
|
+
) -> dict:
|
|
167
|
+
"""
|
|
168
|
+
Implement the actual tool logic.
|
|
169
|
+
Must return a dictionary response.
|
|
170
|
+
"""
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# --- Internal Adapter for Function-Based Tools ---
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _get_schema_from_signature(func: Callable) -> adk_types.Schema:
|
|
178
|
+
"""
|
|
179
|
+
Introspects a function's signature and generates an ADK Schema for its parameters.
|
|
180
|
+
"""
|
|
181
|
+
sig = inspect.signature(func)
|
|
182
|
+
properties = {}
|
|
183
|
+
required = []
|
|
184
|
+
|
|
185
|
+
type_map = {
|
|
186
|
+
str: adk_types.Type.STRING,
|
|
187
|
+
int: adk_types.Type.INTEGER,
|
|
188
|
+
float: adk_types.Type.NUMBER,
|
|
189
|
+
bool: adk_types.Type.BOOLEAN,
|
|
190
|
+
list: adk_types.Type.ARRAY,
|
|
191
|
+
dict: adk_types.Type.OBJECT,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for param in sig.parameters.values():
|
|
195
|
+
if param.name in ("tool_context", "tool_config", "kwargs", "self", "cls"):
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
param_type = param.annotation
|
|
199
|
+
is_optional = False
|
|
200
|
+
|
|
201
|
+
# Handle Optional[T] which is Union[T, None]
|
|
202
|
+
origin = get_origin(param_type)
|
|
203
|
+
args = get_args(param_type)
|
|
204
|
+
if origin is Union and type(None) in args:
|
|
205
|
+
is_optional = True
|
|
206
|
+
# Get the actual type from Union[T, None]
|
|
207
|
+
param_type = next((t for t in args if t is not type(None)), Any)
|
|
208
|
+
|
|
209
|
+
adk_type = type_map.get(param_type)
|
|
210
|
+
if not adk_type:
|
|
211
|
+
# Default to string if type is not supported or specified (e.g., Any)
|
|
212
|
+
adk_type = adk_types.Type.STRING
|
|
213
|
+
|
|
214
|
+
properties[param.name] = adk_types.Schema(type=adk_type, nullable=is_optional)
|
|
215
|
+
|
|
216
|
+
if param.default is inspect.Parameter.empty and not is_optional:
|
|
217
|
+
required.append(param.name)
|
|
218
|
+
|
|
219
|
+
return adk_types.Schema(
|
|
220
|
+
type=adk_types.Type.OBJECT,
|
|
221
|
+
properties=properties,
|
|
222
|
+
required=required,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class _FunctionAsDynamicTool(DynamicTool):
|
|
227
|
+
"""
|
|
228
|
+
Internal adapter to wrap a standard Python function as a DynamicTool.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
def __init__(
|
|
232
|
+
self,
|
|
233
|
+
func: Callable,
|
|
234
|
+
tool_config: Optional[dict] = None,
|
|
235
|
+
provider_instance: Optional[Any] = None,
|
|
236
|
+
):
|
|
237
|
+
super().__init__(tool_config=tool_config)
|
|
238
|
+
self._func = func
|
|
239
|
+
self._provider_instance = provider_instance
|
|
240
|
+
self._schema = _get_schema_from_signature(func)
|
|
241
|
+
|
|
242
|
+
# Check if the function is an instance method that needs `self`
|
|
243
|
+
self._is_instance_method = False
|
|
244
|
+
sig = inspect.signature(self._func)
|
|
245
|
+
if sig.parameters:
|
|
246
|
+
first_param = next(iter(sig.parameters.values()))
|
|
247
|
+
if first_param.name == "self":
|
|
248
|
+
self._is_instance_method = True
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def tool_name(self) -> str:
|
|
252
|
+
return self._func.__name__
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def tool_description(self) -> str:
|
|
256
|
+
return inspect.getdoc(self._func) or ""
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def parameters_schema(self) -> adk_types.Schema:
|
|
260
|
+
return self._schema
|
|
261
|
+
|
|
262
|
+
async def _run_async_impl(
|
|
263
|
+
self,
|
|
264
|
+
args: dict,
|
|
265
|
+
tool_context: ToolContext,
|
|
266
|
+
credential: Optional[str] = None,
|
|
267
|
+
) -> dict:
|
|
268
|
+
# Inject tool_context and tool_config if the function expects them
|
|
269
|
+
sig = inspect.signature(self._func)
|
|
270
|
+
if "tool_context" in sig.parameters:
|
|
271
|
+
args["tool_context"] = tool_context
|
|
272
|
+
if "tool_config" in sig.parameters:
|
|
273
|
+
args["tool_config"] = self.tool_config
|
|
274
|
+
|
|
275
|
+
if self._provider_instance and self._is_instance_method:
|
|
276
|
+
# It's an instance method, call it on the provider instance
|
|
277
|
+
return await self._func(self._provider_instance, **args)
|
|
278
|
+
else:
|
|
279
|
+
# It's a static method or a standalone function
|
|
280
|
+
return await self._func(**args)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# --- Base Class for Tool Providers ---
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class DynamicToolProvider(ABC):
|
|
287
|
+
"""
|
|
288
|
+
Base class for dynamic tool providers that can generate a list of tools
|
|
289
|
+
programmatically from a single configuration block.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
_decorated_tools: List[Callable] = []
|
|
293
|
+
|
|
294
|
+
@classmethod
|
|
295
|
+
def register_tool(cls, func: Callable) -> Callable:
|
|
296
|
+
"""
|
|
297
|
+
A decorator to register a standard async function as a tool.
|
|
298
|
+
The decorated function's signature and docstring will be used to
|
|
299
|
+
create the tool definition.
|
|
300
|
+
"""
|
|
301
|
+
# This check is crucial. It runs for each decorated method.
|
|
302
|
+
# If the current class `cls` is using the list from the base class
|
|
303
|
+
# `DynamicToolProvider`, it creates a new, empty list just for `cls`.
|
|
304
|
+
# On subsequent decorator calls for the same `cls`, this condition will
|
|
305
|
+
# be false, and it will append to the existing list.
|
|
306
|
+
if (
|
|
307
|
+
not hasattr(cls, "_decorated_tools")
|
|
308
|
+
or cls._decorated_tools is DynamicToolProvider._decorated_tools
|
|
309
|
+
):
|
|
310
|
+
cls._decorated_tools = []
|
|
311
|
+
|
|
312
|
+
cls._decorated_tools.append(func)
|
|
313
|
+
return func
|
|
314
|
+
|
|
315
|
+
def _create_tools_from_decorators(
|
|
316
|
+
self, tool_config: Optional[dict] = None
|
|
317
|
+
) -> List[DynamicTool]:
|
|
318
|
+
"""
|
|
319
|
+
Internal helper to convert decorated functions into DynamicTool instances.
|
|
320
|
+
"""
|
|
321
|
+
tools = []
|
|
322
|
+
for func in self._decorated_tools:
|
|
323
|
+
adapter = _FunctionAsDynamicTool(func, tool_config, provider_instance=self)
|
|
324
|
+
tools.append(adapter)
|
|
325
|
+
return tools
|
|
326
|
+
|
|
327
|
+
def get_all_tools_for_framework(
|
|
328
|
+
self, tool_config: Optional[dict] = None
|
|
329
|
+
) -> List[DynamicTool]:
|
|
330
|
+
"""
|
|
331
|
+
Framework-internal method that automatically combines decorated tools with custom tools.
|
|
332
|
+
This is called by the ADK setup code, not by users.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
tool_config: The configuration dictionary from the agent's YAML file.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
A list of all DynamicTool objects (decorated + custom).
|
|
339
|
+
"""
|
|
340
|
+
# Get tools from decorators automatically
|
|
341
|
+
decorated_tools = self._create_tools_from_decorators(tool_config)
|
|
342
|
+
|
|
343
|
+
# Get custom tools from the user's implementation
|
|
344
|
+
custom_tools = self.create_tools(tool_config)
|
|
345
|
+
|
|
346
|
+
return decorated_tools + custom_tools
|
|
347
|
+
|
|
348
|
+
@abstractmethod
|
|
349
|
+
def create_tools(self, tool_config: Optional[dict] = None) -> List[DynamicTool]:
|
|
350
|
+
"""
|
|
351
|
+
Generate and return a list of custom DynamicTool instances.
|
|
352
|
+
|
|
353
|
+
Note: Tools registered with the @register_tool decorator are automatically
|
|
354
|
+
included by the framework - you don't need to handle them here.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
tool_config: The configuration dictionary from the agent's YAML file.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
A list of custom DynamicTool objects (decorated tools are added automatically).
|
|
361
|
+
"""
|
|
362
|
+
pass
|