aip-agents-binary 0.6.5__py3-none-macosx_13_0_arm64.whl → 0.6.6__py3-none-macosx_13_0_arm64.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.
- aip_agents/agent/langgraph_react_agent.py +66 -19
- aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
- aip_agents/examples/hello_world_ptc_custom_tools.pyi +7 -0
- aip_agents/examples/tools/multiply_tool.py +43 -0
- aip_agents/examples/tools/multiply_tool.pyi +18 -0
- aip_agents/ptc/__init__.py +42 -3
- aip_agents/ptc/__init__.pyi +5 -1
- aip_agents/ptc/custom_tools.py +473 -0
- aip_agents/ptc/custom_tools.pyi +184 -0
- aip_agents/ptc/custom_tools_payload.py +400 -0
- aip_agents/ptc/custom_tools_payload.pyi +31 -0
- aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
- aip_agents/ptc/custom_tools_templates/__init__.pyi +0 -0
- aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +23 -0
- aip_agents/ptc/custom_tools_templates/custom_init.py.template +15 -0
- aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +60 -0
- aip_agents/ptc/custom_tools_templates/custom_registry.py.template +87 -0
- aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
- aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +19 -0
- aip_agents/ptc/exceptions.py +18 -0
- aip_agents/ptc/exceptions.pyi +15 -0
- aip_agents/ptc/executor.py +151 -33
- aip_agents/ptc/executor.pyi +34 -8
- aip_agents/ptc/naming.py +13 -1
- aip_agents/ptc/naming.pyi +9 -0
- aip_agents/ptc/prompt_builder.py +118 -16
- aip_agents/ptc/prompt_builder.pyi +12 -8
- aip_agents/ptc/sandbox_bridge.py +206 -8
- aip_agents/ptc/sandbox_bridge.pyi +18 -5
- aip_agents/ptc/tool_def_helpers.py +101 -0
- aip_agents/ptc/tool_def_helpers.pyi +38 -0
- aip_agents/ptc/tool_enrichment.py +163 -0
- aip_agents/ptc/tool_enrichment.pyi +60 -0
- aip_agents/sandbox/defaults.py +197 -1
- aip_agents/sandbox/defaults.pyi +28 -0
- aip_agents/sandbox/e2b_runtime.py +28 -0
- aip_agents/sandbox/e2b_runtime.pyi +7 -1
- aip_agents/sandbox/template_builder.py +2 -2
- aip_agents/tools/execute_ptc_code.py +59 -10
- aip_agents/tools/execute_ptc_code.pyi +5 -5
- {aip_agents_binary-0.6.5.dist-info → aip_agents_binary-0.6.6.dist-info}/METADATA +3 -3
- {aip_agents_binary-0.6.5.dist-info → aip_agents_binary-0.6.6.dist-info}/RECORD +44 -24
- {aip_agents_binary-0.6.5.dist-info → aip_agents_binary-0.6.6.dist-info}/WHEEL +0 -0
- {aip_agents_binary-0.6.5.dist-info → aip_agents_binary-0.6.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Helper functions for constructing PTC tool definitions.
|
|
2
|
+
|
|
3
|
+
Author: Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
4
|
+
|
|
5
|
+
These helpers provide a cleaner API for defining custom tools in PTCCustomToolConfig.
|
|
6
|
+
They return properly-typed dictionaries that can be used in PTCCustomToolConfig.tools.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from aip_agents.ptc import package_tool, file_tool, PTCCustomToolConfig
|
|
10
|
+
>>> config = PTCCustomToolConfig(
|
|
11
|
+
... enabled=True,
|
|
12
|
+
... tools=[
|
|
13
|
+
... package_tool("time_tool", import_path="aip_agents.tools.time_tool", class_name="TimeTool"),
|
|
14
|
+
... file_tool("multiply", file_path="/path/to/multiply.py", class_name="MultiplyTool"),
|
|
15
|
+
... ]
|
|
16
|
+
... )
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from aip_agents.ptc.custom_tools import PTCFileToolDef, PTCPackageToolDef
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def package_tool(
|
|
27
|
+
name: str,
|
|
28
|
+
*,
|
|
29
|
+
import_path: str,
|
|
30
|
+
class_name: str,
|
|
31
|
+
package_path: str | None = None,
|
|
32
|
+
description: str | None = None,
|
|
33
|
+
input_schema: dict[str, Any] | None = None,
|
|
34
|
+
) -> PTCPackageToolDef:
|
|
35
|
+
"""Create a package-based tool definition.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
name: Tool name (will be sanitized for Python compatibility).
|
|
39
|
+
import_path: Python import path (e.g., "aip_agents.tools.time_tool").
|
|
40
|
+
class_name: Tool class name to instantiate.
|
|
41
|
+
package_path: Optional local package path for bundling.
|
|
42
|
+
description: Optional tool description (auto-derived from tool if not provided).
|
|
43
|
+
input_schema: Optional JSON schema (auto-derived from tool if not provided).
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
PTCPackageToolDef dict ready for use in PTCCustomToolConfig.tools.
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
>>> package_tool("time_tool", import_path="aip_agents.tools.time_tool", class_name="TimeTool")
|
|
50
|
+
{'name': 'time_tool', 'kind': 'package', 'import_path': 'aip_agents.tools.time_tool', 'class_name': 'TimeTool'}
|
|
51
|
+
"""
|
|
52
|
+
result: PTCPackageToolDef = {
|
|
53
|
+
"name": name,
|
|
54
|
+
"kind": "package",
|
|
55
|
+
"import_path": import_path,
|
|
56
|
+
"class_name": class_name,
|
|
57
|
+
}
|
|
58
|
+
if package_path is not None:
|
|
59
|
+
result["package_path"] = package_path
|
|
60
|
+
if description is not None:
|
|
61
|
+
result["description"] = description
|
|
62
|
+
if input_schema is not None:
|
|
63
|
+
result["input_schema"] = input_schema
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def file_tool(
|
|
68
|
+
name: str,
|
|
69
|
+
*,
|
|
70
|
+
file_path: str,
|
|
71
|
+
class_name: str,
|
|
72
|
+
description: str | None = None,
|
|
73
|
+
input_schema: dict[str, Any] | None = None,
|
|
74
|
+
) -> PTCFileToolDef:
|
|
75
|
+
"""Create a file-based tool definition.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
name: Tool name (will be sanitized for Python compatibility).
|
|
79
|
+
file_path: Absolute path to the Python file containing the tool.
|
|
80
|
+
class_name: Tool class name to instantiate.
|
|
81
|
+
description: Optional tool description (auto-derived from tool if not provided).
|
|
82
|
+
input_schema: Optional JSON schema (auto-derived from tool if not provided).
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
PTCFileToolDef dict ready for use in PTCCustomToolConfig.tools.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> file_tool("multiply", file_path="/path/to/multiply_tool.py", class_name="MultiplyTool")
|
|
89
|
+
{'name': 'multiply', 'kind': 'file', 'file_path': '/path/to/multiply_tool.py', 'class_name': 'MultiplyTool'}
|
|
90
|
+
"""
|
|
91
|
+
result: PTCFileToolDef = {
|
|
92
|
+
"name": name,
|
|
93
|
+
"kind": "file",
|
|
94
|
+
"file_path": file_path,
|
|
95
|
+
"class_name": class_name,
|
|
96
|
+
}
|
|
97
|
+
if description is not None:
|
|
98
|
+
result["description"] = description
|
|
99
|
+
if input_schema is not None:
|
|
100
|
+
result["input_schema"] = input_schema
|
|
101
|
+
return result
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from aip_agents.ptc.custom_tools import PTCFileToolDef as PTCFileToolDef, PTCPackageToolDef as PTCPackageToolDef
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
def package_tool(name: str, *, import_path: str, class_name: str, package_path: str | None = None, description: str | None = None, input_schema: dict[str, Any] | None = None) -> PTCPackageToolDef:
|
|
5
|
+
'''Create a package-based tool definition.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
name: Tool name (will be sanitized for Python compatibility).
|
|
9
|
+
import_path: Python import path (e.g., "aip_agents.tools.time_tool").
|
|
10
|
+
class_name: Tool class name to instantiate.
|
|
11
|
+
package_path: Optional local package path for bundling.
|
|
12
|
+
description: Optional tool description (auto-derived from tool if not provided).
|
|
13
|
+
input_schema: Optional JSON schema (auto-derived from tool if not provided).
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
PTCPackageToolDef dict ready for use in PTCCustomToolConfig.tools.
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
>>> package_tool("time_tool", import_path="aip_agents.tools.time_tool", class_name="TimeTool")
|
|
20
|
+
{\'name\': \'time_tool\', \'kind\': \'package\', \'import_path\': \'aip_agents.tools.time_tool\', \'class_name\': \'TimeTool\'}
|
|
21
|
+
'''
|
|
22
|
+
def file_tool(name: str, *, file_path: str, class_name: str, description: str | None = None, input_schema: dict[str, Any] | None = None) -> PTCFileToolDef:
|
|
23
|
+
'''Create a file-based tool definition.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
name: Tool name (will be sanitized for Python compatibility).
|
|
27
|
+
file_path: Absolute path to the Python file containing the tool.
|
|
28
|
+
class_name: Tool class name to instantiate.
|
|
29
|
+
description: Optional tool description (auto-derived from tool if not provided).
|
|
30
|
+
input_schema: Optional JSON schema (auto-derived from tool if not provided).
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
PTCFileToolDef dict ready for use in PTCCustomToolConfig.tools.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> file_tool("multiply", file_path="/path/to/multiply_tool.py", class_name="MultiplyTool")
|
|
37
|
+
{\'name\': \'multiply\', \'kind\': \'file\', \'file_path\': \'/path/to/multiply_tool.py\', \'class_name\': \'MultiplyTool\'}
|
|
38
|
+
'''
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Tool metadata enrichment utilities.
|
|
2
|
+
|
|
3
|
+
This module provides functions to enrich custom tool definitions with metadata
|
|
4
|
+
from actual tool objects (name, description, input_schema).
|
|
5
|
+
|
|
6
|
+
Author: Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from aip_agents.ptc.custom_tools import (
|
|
15
|
+
PTCToolDef,
|
|
16
|
+
enrich_tool_def_with_metadata,
|
|
17
|
+
)
|
|
18
|
+
from aip_agents.ptc.naming import sanitize_function_name
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from aip_agents.ptc import PTCCustomToolConfig
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def build_tool_lookup(tools: list[object]) -> dict[str, object]:
|
|
27
|
+
"""Build a lookup map from tool objects for name-based matching.
|
|
28
|
+
|
|
29
|
+
Creates entries for both original and sanitized names to support
|
|
30
|
+
fuzzy matching (e.g., "web-search" matches "web_search").
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
tools: List of tool objects with `name` attribute.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dict mapping tool names (and sanitized names) to tool objects.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
>>> tools = [TimeTool(), WebSearchTool()] # names: "time_tool", "web-search"
|
|
40
|
+
>>> lookup = build_tool_lookup(tools)
|
|
41
|
+
>>> lookup.keys()
|
|
42
|
+
dict_keys(['time_tool', 'web-search', 'web_search'])
|
|
43
|
+
"""
|
|
44
|
+
tool_lookup: dict[str, object] = {}
|
|
45
|
+
|
|
46
|
+
for tool in tools:
|
|
47
|
+
if not hasattr(tool, "name"):
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
tool_name = tool.name
|
|
51
|
+
if not tool_name: # Skip None or empty string names
|
|
52
|
+
continue
|
|
53
|
+
tool_name = str(tool_name)
|
|
54
|
+
tool_lookup[tool_name] = tool
|
|
55
|
+
|
|
56
|
+
# Also add sanitized name for fuzzy matching
|
|
57
|
+
sanitized = sanitize_function_name(tool_name)
|
|
58
|
+
if sanitized != tool_name:
|
|
59
|
+
tool_lookup[sanitized] = tool
|
|
60
|
+
|
|
61
|
+
return tool_lookup
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def match_tool_by_name(
|
|
65
|
+
tool_def: PTCToolDef,
|
|
66
|
+
tool_lookup: dict[str, object],
|
|
67
|
+
) -> tuple[object | None, str | None]:
|
|
68
|
+
"""Find a matching tool object for a tool definition.
|
|
69
|
+
|
|
70
|
+
Tries exact name match first, then falls back to sanitized name.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
tool_def: Tool definition dict with "name" key.
|
|
74
|
+
tool_lookup: Lookup map from build_tool_lookup().
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Tuple of (matching_tool, canonical_name) or (None, None) if no match.
|
|
78
|
+
canonical_name is the tool object's actual name (may differ from tool_def name).
|
|
79
|
+
"""
|
|
80
|
+
def_name = tool_def.get("name", "")
|
|
81
|
+
sanitized_def_name = sanitize_function_name(def_name)
|
|
82
|
+
|
|
83
|
+
# Try exact match first, then sanitized
|
|
84
|
+
matching_tool = tool_lookup.get(def_name) or tool_lookup.get(sanitized_def_name)
|
|
85
|
+
|
|
86
|
+
if matching_tool is None:
|
|
87
|
+
return None, None
|
|
88
|
+
|
|
89
|
+
# Get canonical name from tool object
|
|
90
|
+
canonical_name = str(getattr(matching_tool, "name", ""))
|
|
91
|
+
|
|
92
|
+
# Treat empty canonical name as no match
|
|
93
|
+
if not canonical_name:
|
|
94
|
+
return None, None
|
|
95
|
+
|
|
96
|
+
return matching_tool, canonical_name
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def enrich_custom_tools_from_agent(
|
|
100
|
+
custom_tools_config: PTCCustomToolConfig,
|
|
101
|
+
agent_tools: list[object],
|
|
102
|
+
agent_name: str = "",
|
|
103
|
+
) -> int:
|
|
104
|
+
"""Enrich custom tool definitions with metadata from agent's tool objects.
|
|
105
|
+
|
|
106
|
+
Matches custom tool definitions with actual tool objects by name (including
|
|
107
|
+
sanitized name matching) and enriches them with description and input_schema.
|
|
108
|
+
|
|
109
|
+
Note: This function modifies custom_tools_config.tools in-place.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
custom_tools_config: The PTCCustomToolConfig to enrich.
|
|
113
|
+
agent_tools: List of tool objects from the agent.
|
|
114
|
+
agent_name: Agent name for logging context.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Number of tools that were enriched.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> config = PTCCustomToolConfig(enabled=True, tools=[{"name": "multiply", ...}])
|
|
121
|
+
>>> count = enrich_custom_tools_from_agent(config, [MultiplyTool()], "my_agent")
|
|
122
|
+
>>> count
|
|
123
|
+
1
|
|
124
|
+
"""
|
|
125
|
+
if not custom_tools_config.enabled:
|
|
126
|
+
return 0
|
|
127
|
+
|
|
128
|
+
if not custom_tools_config.tools:
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
# Build lookup map
|
|
132
|
+
tool_lookup = build_tool_lookup(agent_tools)
|
|
133
|
+
|
|
134
|
+
# Enrich each tool definition
|
|
135
|
+
enriched_count = 0
|
|
136
|
+
log_prefix = f"Agent '{agent_name}': " if agent_name else ""
|
|
137
|
+
|
|
138
|
+
for i, tool_def in enumerate(custom_tools_config.tools):
|
|
139
|
+
def_name = tool_def.get("name", "")
|
|
140
|
+
matching_tool, canonical_name = match_tool_by_name(tool_def, tool_lookup)
|
|
141
|
+
|
|
142
|
+
if matching_tool is None:
|
|
143
|
+
logger.debug(
|
|
144
|
+
f"{log_prefix}No matching tool object found for custom tool '{def_name}', using existing definition"
|
|
145
|
+
)
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
# Handle name correction if matched by sanitized name
|
|
149
|
+
if canonical_name and canonical_name != def_name:
|
|
150
|
+
tool_def_copy = dict(tool_def)
|
|
151
|
+
tool_def_copy["name"] = canonical_name
|
|
152
|
+
enriched = enrich_tool_def_with_metadata(tool_def_copy, matching_tool)
|
|
153
|
+
else:
|
|
154
|
+
enriched = enrich_tool_def_with_metadata(tool_def, matching_tool)
|
|
155
|
+
|
|
156
|
+
custom_tools_config.tools[i] = enriched
|
|
157
|
+
enriched_count += 1
|
|
158
|
+
logger.debug(f"{log_prefix}Enriched custom tool '{def_name}' with metadata")
|
|
159
|
+
|
|
160
|
+
if enriched_count > 0:
|
|
161
|
+
logger.info(f"{log_prefix}Enriched {enriched_count} custom tool(s) with metadata")
|
|
162
|
+
|
|
163
|
+
return enriched_count
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from aip_agents.ptc import PTCCustomToolConfig as PTCCustomToolConfig
|
|
3
|
+
from aip_agents.ptc.custom_tools import PTCToolDef as PTCToolDef, enrich_tool_def_with_metadata as enrich_tool_def_with_metadata
|
|
4
|
+
from aip_agents.ptc.naming import sanitize_function_name as sanitize_function_name
|
|
5
|
+
|
|
6
|
+
logger: Incomplete
|
|
7
|
+
|
|
8
|
+
def build_tool_lookup(tools: list[object]) -> dict[str, object]:
|
|
9
|
+
'''Build a lookup map from tool objects for name-based matching.
|
|
10
|
+
|
|
11
|
+
Creates entries for both original and sanitized names to support
|
|
12
|
+
fuzzy matching (e.g., "web-search" matches "web_search").
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
tools: List of tool objects with `name` attribute.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Dict mapping tool names (and sanitized names) to tool objects.
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
>>> tools = [TimeTool(), WebSearchTool()] # names: "time_tool", "web-search"
|
|
22
|
+
>>> lookup = build_tool_lookup(tools)
|
|
23
|
+
>>> lookup.keys()
|
|
24
|
+
dict_keys([\'time_tool\', \'web-search\', \'web_search\'])
|
|
25
|
+
'''
|
|
26
|
+
def match_tool_by_name(tool_def: PTCToolDef, tool_lookup: dict[str, object]) -> tuple[object | None, str | None]:
|
|
27
|
+
'''Find a matching tool object for a tool definition.
|
|
28
|
+
|
|
29
|
+
Tries exact name match first, then falls back to sanitized name.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
tool_def: Tool definition dict with "name" key.
|
|
33
|
+
tool_lookup: Lookup map from build_tool_lookup().
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Tuple of (matching_tool, canonical_name) or (None, None) if no match.
|
|
37
|
+
canonical_name is the tool object\'s actual name (may differ from tool_def name).
|
|
38
|
+
'''
|
|
39
|
+
def enrich_custom_tools_from_agent(custom_tools_config: PTCCustomToolConfig, agent_tools: list[object], agent_name: str = '') -> int:
|
|
40
|
+
'''Enrich custom tool definitions with metadata from agent\'s tool objects.
|
|
41
|
+
|
|
42
|
+
Matches custom tool definitions with actual tool objects by name (including
|
|
43
|
+
sanitized name matching) and enriches them with description and input_schema.
|
|
44
|
+
|
|
45
|
+
Note: This function modifies custom_tools_config.tools in-place.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
custom_tools_config: The PTCCustomToolConfig to enrich.
|
|
49
|
+
agent_tools: List of tool objects from the agent.
|
|
50
|
+
agent_name: Agent name for logging context.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Number of tools that were enriched.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> config = PTCCustomToolConfig(enabled=True, tools=[{"name": "multiply", ...}])
|
|
57
|
+
>>> count = enrich_custom_tools_from_agent(config, [MultiplyTool()], "my_agent")
|
|
58
|
+
>>> count
|
|
59
|
+
1
|
|
60
|
+
'''
|
aip_agents/sandbox/defaults.py
CHANGED
|
@@ -1,9 +1,205 @@
|
|
|
1
1
|
"""Defaults for PTC sandbox templates and packages."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from aip_agents.mcp.client.base_mcp_client import BaseMCPClient
|
|
9
|
+
from aip_agents.ptc.custom_tools import PTCCustomToolConfig
|
|
10
|
+
|
|
3
11
|
DEFAULT_PTC_TEMPLATE = "aip-agents-ptc-v1"
|
|
12
|
+
AIP_AGENTS_BINARY_LOCAL = "aip-agents-binary[local]"
|
|
4
13
|
DEFAULT_PTC_PACKAGES: tuple[str, ...] = (
|
|
5
|
-
|
|
14
|
+
AIP_AGENTS_BINARY_LOCAL,
|
|
6
15
|
"mcp",
|
|
7
16
|
"httpx",
|
|
8
17
|
"gllm-plugin-binary==0.0.7",
|
|
9
18
|
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _has_mcp_tools(mcp_client: BaseMCPClient | None) -> bool:
|
|
22
|
+
"""Check if MCP tools are present.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
mcp_client: The MCP client to check.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
True if MCP client has servers configured.
|
|
29
|
+
"""
|
|
30
|
+
return mcp_client is not None and bool(getattr(mcp_client, "servers", None))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _has_custom_tools(custom_tools_config: PTCCustomToolConfig | None) -> bool:
|
|
34
|
+
"""Check if custom tools are present and enabled.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
custom_tools_config: The custom tools configuration to check.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
True if custom tools are enabled and tools list is non-empty.
|
|
41
|
+
"""
|
|
42
|
+
return custom_tools_config is not None and custom_tools_config.enabled and bool(custom_tools_config.tools)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _has_native_aip_tools(custom_tools_config: PTCCustomToolConfig | None) -> bool:
|
|
46
|
+
"""Check if native AIP tools are present.
|
|
47
|
+
|
|
48
|
+
Native AIP tools are package-based custom tools with import_path starting with "aip_agents.tools.".
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
custom_tools_config: The custom tools configuration to check.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
True if at least one native AIP tool is found.
|
|
55
|
+
"""
|
|
56
|
+
if not _has_custom_tools(custom_tools_config):
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
assert custom_tools_config is not None
|
|
60
|
+
for tool in custom_tools_config.tools:
|
|
61
|
+
if tool.get("kind") == "package":
|
|
62
|
+
import_path = tool.get("import_path", "")
|
|
63
|
+
if import_path.startswith("aip_agents.tools."):
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _merge_requirements(packages: list[str], requirements: list[str]) -> None:
|
|
70
|
+
"""Merge requirements into packages list, avoiding duplicates.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
packages: Target list to merge into (modified in place).
|
|
74
|
+
requirements: Requirements to merge.
|
|
75
|
+
"""
|
|
76
|
+
for req in requirements:
|
|
77
|
+
if req not in packages:
|
|
78
|
+
packages.append(req)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _merge_custom_requirements(
|
|
82
|
+
packages: list[str],
|
|
83
|
+
custom_tools_config: PTCCustomToolConfig | None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Merge custom tool requirements into packages list.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
packages: Target list to merge into (modified in place).
|
|
89
|
+
custom_tools_config: Custom tools configuration.
|
|
90
|
+
"""
|
|
91
|
+
if not _has_custom_tools(custom_tools_config):
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
assert custom_tools_config is not None
|
|
95
|
+
if custom_tools_config.requirements:
|
|
96
|
+
_merge_requirements(packages, custom_tools_config.requirements)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _packages_for_default_template(
|
|
100
|
+
custom_tools_config: PTCCustomToolConfig | None,
|
|
101
|
+
user_ptc_packages: list[str] | None,
|
|
102
|
+
) -> list[str] | None:
|
|
103
|
+
"""Build package list when using the default template.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
custom_tools_config: Custom tools configuration.
|
|
107
|
+
user_ptc_packages: Explicitly provided packages by user (None if not set).
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
List of packages to install, or None to skip installation.
|
|
111
|
+
"""
|
|
112
|
+
packages = list(user_ptc_packages) if user_ptc_packages is not None else []
|
|
113
|
+
_merge_custom_requirements(packages, custom_tools_config)
|
|
114
|
+
return packages if packages else None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _packages_for_other_templates(
|
|
118
|
+
*,
|
|
119
|
+
mcp_client: BaseMCPClient | None,
|
|
120
|
+
custom_tools_config: PTCCustomToolConfig | None,
|
|
121
|
+
user_ptc_packages: list[str] | None,
|
|
122
|
+
) -> list[str] | None:
|
|
123
|
+
"""Build package list when using a non-default template.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
mcp_client: The MCP client (check if has servers for MCP tools).
|
|
127
|
+
custom_tools_config: Custom tools configuration.
|
|
128
|
+
user_ptc_packages: Explicitly provided packages by user (None if not set).
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
List of packages to install, or None to skip installation.
|
|
132
|
+
"""
|
|
133
|
+
packages = list(user_ptc_packages) if user_ptc_packages is not None else []
|
|
134
|
+
base_packages = _build_base_packages(mcp_client=mcp_client, custom_tools_config=custom_tools_config)
|
|
135
|
+
_merge_requirements(packages, base_packages)
|
|
136
|
+
_merge_custom_requirements(packages, custom_tools_config)
|
|
137
|
+
return packages if packages else None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _build_base_packages(
|
|
141
|
+
*,
|
|
142
|
+
mcp_client: BaseMCPClient | None,
|
|
143
|
+
custom_tools_config: PTCCustomToolConfig | None,
|
|
144
|
+
) -> list[str]:
|
|
145
|
+
"""Build base package list based on tool types.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
mcp_client: The MCP client (check if has servers for MCP tools).
|
|
149
|
+
custom_tools_config: Custom tools configuration.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of base packages needed for the tool types present.
|
|
153
|
+
"""
|
|
154
|
+
packages: list[str] = []
|
|
155
|
+
|
|
156
|
+
if _has_mcp_tools(mcp_client):
|
|
157
|
+
packages.extend(["mcp", "httpx"])
|
|
158
|
+
|
|
159
|
+
if _has_custom_tools(custom_tools_config):
|
|
160
|
+
packages.extend(["langchain", "gllm-plugin-binary==0.0.7"])
|
|
161
|
+
|
|
162
|
+
if _has_native_aip_tools(custom_tools_config):
|
|
163
|
+
if AIP_AGENTS_BINARY_LOCAL not in packages:
|
|
164
|
+
packages.append(AIP_AGENTS_BINARY_LOCAL)
|
|
165
|
+
|
|
166
|
+
return packages
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def select_sandbox_packages(
|
|
170
|
+
*,
|
|
171
|
+
mcp_client: BaseMCPClient | None,
|
|
172
|
+
custom_tools_config: PTCCustomToolConfig | None,
|
|
173
|
+
template: str | None,
|
|
174
|
+
user_ptc_packages: list[str] | None,
|
|
175
|
+
) -> list[str] | None:
|
|
176
|
+
"""Select minimal packages to install in sandbox based on tool types.
|
|
177
|
+
|
|
178
|
+
This function implements the smart package selection logic:
|
|
179
|
+
- DEFAULT_PTC_TEMPLATE + no user packages: Skip install (template has all deps)
|
|
180
|
+
- DEFAULT_PTC_TEMPLATE + user packages: User packages + PTCCustomToolConfig.requirements
|
|
181
|
+
- Other template + no user packages: Build packages from tool types
|
|
182
|
+
- Other template + user packages: User packages + tool-type packages + PTCCustomToolConfig.requirements
|
|
183
|
+
|
|
184
|
+
Key principle: Tools won't work without their required packages. Always ensure:
|
|
185
|
+
- MCP tools → mcp + httpx installed
|
|
186
|
+
- Custom LangChain tools → langchain + gllm-plugin-binary installed
|
|
187
|
+
- Native AIP tools → aip-agents-binary[local] installed
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
mcp_client: The MCP client (check if has servers for MCP tools).
|
|
191
|
+
custom_tools_config: Custom tools configuration (includes tools list and .requirements field).
|
|
192
|
+
template: Sandbox template being used.
|
|
193
|
+
user_ptc_packages: Explicitly provided packages by user (None if not set).
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List of packages to install, or None to skip installation.
|
|
197
|
+
"""
|
|
198
|
+
if template == DEFAULT_PTC_TEMPLATE:
|
|
199
|
+
return _packages_for_default_template(custom_tools_config, user_ptc_packages)
|
|
200
|
+
|
|
201
|
+
return _packages_for_other_templates(
|
|
202
|
+
mcp_client=mcp_client,
|
|
203
|
+
custom_tools_config=custom_tools_config,
|
|
204
|
+
user_ptc_packages=user_ptc_packages,
|
|
205
|
+
)
|
aip_agents/sandbox/defaults.pyi
CHANGED
|
@@ -1,2 +1,30 @@
|
|
|
1
|
+
from aip_agents.mcp.client.base_mcp_client import BaseMCPClient as BaseMCPClient
|
|
2
|
+
from aip_agents.ptc.custom_tools import PTCCustomToolConfig as PTCCustomToolConfig
|
|
3
|
+
|
|
1
4
|
DEFAULT_PTC_TEMPLATE: str
|
|
5
|
+
AIP_AGENTS_BINARY_LOCAL: str
|
|
2
6
|
DEFAULT_PTC_PACKAGES: tuple[str, ...]
|
|
7
|
+
|
|
8
|
+
def select_sandbox_packages(*, mcp_client: BaseMCPClient | None, custom_tools_config: PTCCustomToolConfig | None, template: str | None, user_ptc_packages: list[str] | None) -> list[str] | None:
|
|
9
|
+
"""Select minimal packages to install in sandbox based on tool types.
|
|
10
|
+
|
|
11
|
+
This function implements the smart package selection logic:
|
|
12
|
+
- DEFAULT_PTC_TEMPLATE + no user packages: Skip install (template has all deps)
|
|
13
|
+
- DEFAULT_PTC_TEMPLATE + user packages: User packages + PTCCustomToolConfig.requirements
|
|
14
|
+
- Other template + no user packages: Build packages from tool types
|
|
15
|
+
- Other template + user packages: User packages + tool-type packages + PTCCustomToolConfig.requirements
|
|
16
|
+
|
|
17
|
+
Key principle: Tools won't work without their required packages. Always ensure:
|
|
18
|
+
- MCP tools → mcp + httpx installed
|
|
19
|
+
- Custom LangChain tools → langchain + gllm-plugin-binary installed
|
|
20
|
+
- Native AIP tools → aip-agents-binary[local] installed
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
mcp_client: The MCP client (check if has servers for MCP tools).
|
|
24
|
+
custom_tools_config: Custom tools configuration (includes tools list and .requirements field).
|
|
25
|
+
template: Sandbox template being used.
|
|
26
|
+
user_ptc_packages: Explicitly provided packages by user (None if not set).
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of packages to install, or None to skip installation.
|
|
30
|
+
"""
|
|
@@ -6,6 +6,8 @@ Authors:
|
|
|
6
6
|
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
|
|
9
11
|
from e2b_code_interpreter import AsyncSandbox, OutputMessage
|
|
10
12
|
|
|
11
13
|
from aip_agents.sandbox.defaults import DEFAULT_PTC_PACKAGES, DEFAULT_PTC_TEMPLATE
|
|
@@ -13,6 +15,10 @@ from aip_agents.sandbox.types import SandboxExecutionResult
|
|
|
13
15
|
from aip_agents.sandbox.validation import validate_package_names
|
|
14
16
|
from aip_agents.utils.logger import get_logger
|
|
15
17
|
|
|
18
|
+
# Type alias for package selector callback.
|
|
19
|
+
# Called after sandbox creation with actual template status (None if template failed).
|
|
20
|
+
PackageSelector = Callable[[str | None], list[str] | None]
|
|
21
|
+
|
|
16
22
|
logger = get_logger(__name__)
|
|
17
23
|
|
|
18
24
|
SANDBOX_NOT_INITIALIZED_ERROR = "Sandbox not initialized"
|
|
@@ -40,15 +46,21 @@ class E2BSandboxRuntime:
|
|
|
40
46
|
self,
|
|
41
47
|
template: str | None = None,
|
|
42
48
|
ptc_packages: list[str] | None = None,
|
|
49
|
+
package_selector: PackageSelector | None = None,
|
|
43
50
|
) -> None:
|
|
44
51
|
"""Initialize E2B sandbox runtime.
|
|
45
52
|
|
|
46
53
|
Args:
|
|
47
54
|
template: Optional E2B template ID for custom sandbox environments.
|
|
48
55
|
ptc_packages: Packages to install in sandbox. If None or empty, skip install.
|
|
56
|
+
Overwritten by package_selector if provided.
|
|
57
|
+
package_selector: Optional callback to select packages after sandbox creation.
|
|
58
|
+
Called with actual template (None if template failed) to enable smart
|
|
59
|
+
package selection based on whether template was successfully created.
|
|
49
60
|
"""
|
|
50
61
|
self._template = template
|
|
51
62
|
self._ptc_packages = ptc_packages
|
|
63
|
+
self._package_selector = package_selector
|
|
52
64
|
self._sandbox: AsyncSandbox | None = None
|
|
53
65
|
self._sandbox_created_with_template = False
|
|
54
66
|
|
|
@@ -153,6 +165,22 @@ class E2BSandboxRuntime:
|
|
|
153
165
|
self._sandbox_created_with_template = False
|
|
154
166
|
await create_default_sandbox()
|
|
155
167
|
|
|
168
|
+
# Determine packages to install
|
|
169
|
+
# If package_selector is provided, use it with actual template status
|
|
170
|
+
# This enables smart package selection based on whether template succeeded
|
|
171
|
+
if self._package_selector is not None:
|
|
172
|
+
actual_template = template if self._sandbox_created_with_template else None
|
|
173
|
+
try:
|
|
174
|
+
self._ptc_packages = self._package_selector(actual_template)
|
|
175
|
+
logger.info(
|
|
176
|
+
f"Package selector called with actual_template={actual_template}, "
|
|
177
|
+
f"selected packages: {self._ptc_packages}"
|
|
178
|
+
)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(f"Package selector failed: {e}")
|
|
181
|
+
await self.cleanup()
|
|
182
|
+
raise
|
|
183
|
+
|
|
156
184
|
# Install ptc_packages if non-empty
|
|
157
185
|
if self._ptc_packages:
|
|
158
186
|
if self._should_skip_default_ptc_install(template):
|