sandboxy 0.0.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.
- sandboxy/__init__.py +3 -0
- sandboxy/agents/__init__.py +21 -0
- sandboxy/agents/base.py +66 -0
- sandboxy/agents/llm_prompt.py +308 -0
- sandboxy/agents/loader.py +222 -0
- sandboxy/api/__init__.py +5 -0
- sandboxy/api/app.py +76 -0
- sandboxy/api/routes/__init__.py +1 -0
- sandboxy/api/routes/agents.py +92 -0
- sandboxy/api/routes/local.py +1388 -0
- sandboxy/api/routes/tools.py +106 -0
- sandboxy/cli/__init__.py +1 -0
- sandboxy/cli/main.py +1196 -0
- sandboxy/cli/type_detector.py +48 -0
- sandboxy/config.py +49 -0
- sandboxy/core/__init__.py +1 -0
- sandboxy/core/async_runner.py +824 -0
- sandboxy/core/mdl_parser.py +441 -0
- sandboxy/core/runner.py +599 -0
- sandboxy/core/safe_eval.py +165 -0
- sandboxy/core/state.py +234 -0
- sandboxy/datasets/__init__.py +20 -0
- sandboxy/datasets/loader.py +193 -0
- sandboxy/datasets/runner.py +442 -0
- sandboxy/errors.py +166 -0
- sandboxy/local/context.py +235 -0
- sandboxy/local/results.py +173 -0
- sandboxy/logging.py +31 -0
- sandboxy/mcp/__init__.py +25 -0
- sandboxy/mcp/client.py +360 -0
- sandboxy/mcp/wrapper.py +99 -0
- sandboxy/providers/__init__.py +34 -0
- sandboxy/providers/anthropic_provider.py +271 -0
- sandboxy/providers/base.py +123 -0
- sandboxy/providers/http_client.py +101 -0
- sandboxy/providers/openai_provider.py +282 -0
- sandboxy/providers/openrouter.py +958 -0
- sandboxy/providers/registry.py +199 -0
- sandboxy/scenarios/__init__.py +11 -0
- sandboxy/scenarios/comparison.py +491 -0
- sandboxy/scenarios/loader.py +262 -0
- sandboxy/scenarios/runner.py +468 -0
- sandboxy/scenarios/unified.py +1434 -0
- sandboxy/session/__init__.py +21 -0
- sandboxy/session/manager.py +278 -0
- sandboxy/tools/__init__.py +34 -0
- sandboxy/tools/base.py +127 -0
- sandboxy/tools/loader.py +270 -0
- sandboxy/tools/yaml_tools.py +708 -0
- sandboxy/ui/__init__.py +27 -0
- sandboxy/ui/dist/assets/index-CgAkYWrJ.css +1 -0
- sandboxy/ui/dist/assets/index-D4zoGFcr.js +347 -0
- sandboxy/ui/dist/index.html +14 -0
- sandboxy/utils/__init__.py +3 -0
- sandboxy/utils/time.py +20 -0
- sandboxy-0.0.1.dist-info/METADATA +241 -0
- sandboxy-0.0.1.dist-info/RECORD +60 -0
- sandboxy-0.0.1.dist-info/WHEEL +4 -0
- sandboxy-0.0.1.dist-info/entry_points.txt +3 -0
- sandboxy-0.0.1.dist-info/licenses/LICENSE +201 -0
sandboxy/tools/loader.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Tool loader - dynamically loads tool implementations from specs.
|
|
2
|
+
|
|
3
|
+
This module handles:
|
|
4
|
+
- Loading tool specifications from YAML files
|
|
5
|
+
- Creating tool instances from environment configuration
|
|
6
|
+
- Loading YAML tool libraries
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import importlib
|
|
12
|
+
import logging
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
from sandboxy.core.state import EnvConfig
|
|
19
|
+
from sandboxy.tools.base import BaseTool, Tool, ToolConfig
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Default directories to search for tool specs
|
|
24
|
+
TOOLS_DIRS = [
|
|
25
|
+
Path("tools/core"),
|
|
26
|
+
Path("tools/community"),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# Default directories for YAML tool libraries
|
|
30
|
+
YAML_TOOL_DIRS = [
|
|
31
|
+
Path("tools"),
|
|
32
|
+
Path("sandboxy/tools/libraries"),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_tool_dirs() -> list[Path]:
|
|
37
|
+
"""Get tool directories, including local context if available.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
List of directories to search for YAML tool libraries.
|
|
41
|
+
"""
|
|
42
|
+
from sandboxy.local.context import get_local_context, is_local_mode
|
|
43
|
+
|
|
44
|
+
dirs: list[Path] = []
|
|
45
|
+
|
|
46
|
+
if is_local_mode():
|
|
47
|
+
ctx = get_local_context()
|
|
48
|
+
if ctx and ctx.tools_dir.exists():
|
|
49
|
+
dirs.append(ctx.tools_dir)
|
|
50
|
+
|
|
51
|
+
dirs.extend(YAML_TOOL_DIRS)
|
|
52
|
+
return dirs
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Built-in tool mappings (type -> module:class)
|
|
56
|
+
BUILTIN_TOOLS: dict[str, str] = {
|
|
57
|
+
"mock_lemonade": "sandboxy.tools.mock_lemonade:MockLemonadeTool",
|
|
58
|
+
"mock_store": "sandboxy.tools.mock_store:MockStoreTool",
|
|
59
|
+
"mock_wedding": "sandboxy.tools.mock_wedding:MockWeddingTool",
|
|
60
|
+
"mock_customer_support": "sandboxy.tools.mock_customer_support:MockCustomerSupportTool",
|
|
61
|
+
"mock_mechanic": "sandboxy.tools.mock_mechanic:MockMechanicTool",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _load_tool_specs(dirs: list[Path] | None = None) -> dict[str, dict[str, Any]]:
|
|
66
|
+
"""Load tool specifications from YAML files.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
dirs: Directories to search for tool specs. Uses TOOLS_DIRS if None.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Dictionary mapping tool type to spec.
|
|
73
|
+
"""
|
|
74
|
+
if dirs is None:
|
|
75
|
+
dirs = TOOLS_DIRS
|
|
76
|
+
|
|
77
|
+
specs: dict[str, dict[str, Any]] = {}
|
|
78
|
+
for d in dirs:
|
|
79
|
+
if not d.exists():
|
|
80
|
+
continue
|
|
81
|
+
for path in d.glob("**/*.yaml"):
|
|
82
|
+
try:
|
|
83
|
+
raw = yaml.safe_load(path.read_text())
|
|
84
|
+
if raw and "type" in raw:
|
|
85
|
+
specs[raw["type"]] = raw
|
|
86
|
+
except yaml.YAMLError as e:
|
|
87
|
+
logger.warning("Failed to parse tool spec %s: %s", path, e)
|
|
88
|
+
continue
|
|
89
|
+
for path in d.glob("**/*.yml"):
|
|
90
|
+
try:
|
|
91
|
+
raw = yaml.safe_load(path.read_text())
|
|
92
|
+
if raw and "type" in raw:
|
|
93
|
+
specs[raw["type"]] = raw
|
|
94
|
+
except yaml.YAMLError as e:
|
|
95
|
+
logger.warning("Failed to parse tool spec %s: %s", path, e)
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
return specs
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def load_tool_class(module_path: str) -> type[BaseTool]:
|
|
102
|
+
"""Load a tool class from a module path.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
module_path: Path in format "module.path:ClassName".
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Tool class.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ImportError: If module cannot be imported.
|
|
112
|
+
AttributeError: If class not found in module.
|
|
113
|
+
"""
|
|
114
|
+
module_name, class_name = module_path.split(":")
|
|
115
|
+
mod = importlib.import_module(module_name)
|
|
116
|
+
return getattr(mod, class_name)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ToolLoader:
|
|
120
|
+
"""Loader for creating tool instances from environment config."""
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def from_env_config(
|
|
124
|
+
cls,
|
|
125
|
+
env: EnvConfig,
|
|
126
|
+
tool_dirs: list[Path] | None = None,
|
|
127
|
+
) -> dict[str, Tool]:
|
|
128
|
+
"""Create tool instances from environment configuration.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
env: Environment configuration containing tool references.
|
|
132
|
+
tool_dirs: Optional directories to search for tool specs.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dictionary mapping tool name to tool instance.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: If a tool type cannot be found.
|
|
139
|
+
"""
|
|
140
|
+
specs = _load_tool_specs(tool_dirs)
|
|
141
|
+
tools: dict[str, Tool] = {}
|
|
142
|
+
|
|
143
|
+
for tool_ref in env.tools:
|
|
144
|
+
# First check built-in tools
|
|
145
|
+
if tool_ref.type in BUILTIN_TOOLS:
|
|
146
|
+
module_path = BUILTIN_TOOLS[tool_ref.type]
|
|
147
|
+
# Then check loaded specs
|
|
148
|
+
elif tool_ref.type in specs:
|
|
149
|
+
spec = specs[tool_ref.type]
|
|
150
|
+
if "impl" not in spec or "module" not in spec["impl"]:
|
|
151
|
+
msg = f"Tool spec for '{tool_ref.type}' missing impl.module"
|
|
152
|
+
raise ValueError(msg)
|
|
153
|
+
module_path = spec["impl"]["module"]
|
|
154
|
+
else:
|
|
155
|
+
msg = f"Unknown tool type: {tool_ref.type}"
|
|
156
|
+
raise ValueError(msg)
|
|
157
|
+
|
|
158
|
+
# Load and instantiate the tool class
|
|
159
|
+
tool_cls = load_tool_class(module_path)
|
|
160
|
+
config = ToolConfig(
|
|
161
|
+
name=tool_ref.name,
|
|
162
|
+
type=tool_ref.type,
|
|
163
|
+
description=tool_ref.description,
|
|
164
|
+
config=tool_ref.config,
|
|
165
|
+
)
|
|
166
|
+
tools[tool_ref.name] = tool_cls(config)
|
|
167
|
+
|
|
168
|
+
return tools
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def get_available_tools(cls, tool_dirs: list[Path] | None = None) -> list[str]:
|
|
172
|
+
"""Get list of available tool types.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
tool_dirs: Optional directories to search for tool specs.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of available tool type names.
|
|
179
|
+
"""
|
|
180
|
+
specs = _load_tool_specs(tool_dirs)
|
|
181
|
+
available = list(BUILTIN_TOOLS.keys())
|
|
182
|
+
available.extend(specs.keys())
|
|
183
|
+
|
|
184
|
+
# Also list YAML tool libraries
|
|
185
|
+
yaml_libs = get_yaml_tool_libraries()
|
|
186
|
+
available.extend(yaml_libs)
|
|
187
|
+
|
|
188
|
+
return sorted(set(available))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# -----------------------------------------------------------------------------
|
|
192
|
+
# YAML Tool Library Support
|
|
193
|
+
# -----------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def get_yaml_tool_libraries(tool_dirs: list[Path] | None = None) -> list[str]:
|
|
197
|
+
"""Get list of available YAML tool library names.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
tool_dirs: Directories to search. Uses YAML_TOOL_DIRS if None.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
List of library names (without extension).
|
|
204
|
+
"""
|
|
205
|
+
if tool_dirs is None:
|
|
206
|
+
tool_dirs = YAML_TOOL_DIRS
|
|
207
|
+
|
|
208
|
+
libraries: list[str] = []
|
|
209
|
+
for d in tool_dirs:
|
|
210
|
+
if not d.exists():
|
|
211
|
+
continue
|
|
212
|
+
for ext in (".yml", ".yaml"):
|
|
213
|
+
for path in d.glob(f"*{ext}"):
|
|
214
|
+
# Only include files that start with mock_ or have tools: key
|
|
215
|
+
try:
|
|
216
|
+
raw = yaml.safe_load(path.read_text())
|
|
217
|
+
if raw and ("tools" in raw or path.stem.startswith("mock_")):
|
|
218
|
+
libraries.append(path.stem)
|
|
219
|
+
except yaml.YAMLError as e:
|
|
220
|
+
logger.warning("Failed to parse tool library %s: %s", path, e)
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
return libraries
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def load_yaml_tools_from_scenario(
|
|
227
|
+
scenario_data: dict[str, Any],
|
|
228
|
+
tool_dirs: list[Path] | None = None,
|
|
229
|
+
) -> dict[str, Tool]:
|
|
230
|
+
"""Load YAML-defined tools from a scenario definition.
|
|
231
|
+
|
|
232
|
+
This handles both `tools_from` library references and inline `tools` definitions.
|
|
233
|
+
Use this when loading scenarios that define their own tools.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
scenario_data: Parsed scenario YAML containing tools and/or tools_from
|
|
237
|
+
tool_dirs: Optional directories to search for tool libraries
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Dictionary of tool name to tool instance
|
|
241
|
+
"""
|
|
242
|
+
from sandboxy.tools.yaml_tools import load_scenario_tools
|
|
243
|
+
|
|
244
|
+
if tool_dirs is None:
|
|
245
|
+
tool_dirs = YAML_TOOL_DIRS
|
|
246
|
+
|
|
247
|
+
return load_scenario_tools(scenario_data, tool_dirs)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def load_yaml_tool_library(
|
|
251
|
+
library_name: str,
|
|
252
|
+
tool_dirs: list[Path] | None = None,
|
|
253
|
+
) -> dict[str, Tool]:
|
|
254
|
+
"""Load all tools from a YAML tool library.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
library_name: Name of the library (without extension)
|
|
258
|
+
tool_dirs: Optional directories to search
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Dictionary of tool name to tool instance
|
|
262
|
+
"""
|
|
263
|
+
from sandboxy.tools.yaml_tools import YamlToolLoader
|
|
264
|
+
|
|
265
|
+
if tool_dirs is None:
|
|
266
|
+
tool_dirs = YAML_TOOL_DIRS
|
|
267
|
+
|
|
268
|
+
loader = YamlToolLoader(tool_dirs)
|
|
269
|
+
library = loader.load_library(library_name)
|
|
270
|
+
return loader.create_tool_instances(library.tools)
|