Custom Tool¶
Must implement¶
required_sandbox_types: list of sandbox types this tool can run in (None= any).async def execute(self, sandbox_context, **kwargs): the tool logic. Return aToolResultor a plain dict (the framework will wrap it).- Optional constructor args:
name / description / parameters / enabled / timeout.
Compatibility between a tool and a sandbox is decided by Tool.is_compatible_with_sandbox, which combines required_sandbox_types with SandboxType.is_compatible.
Example A: minimal tool (no sandbox commands)¶
from typing import Any, Dict, List, Optional
from ms_enclave.sandbox.tools.base import Tool, register_tool
from ms_enclave.sandbox.model import SandboxType
@register_tool('hello')
class HelloTool(Tool):
def __init__(self, name: str = 'hello', description: str = 'Say hello', enabled: bool = True):
super().__init__(name=name, description=description, enabled=enabled)
@property
def required_sandbox_types(self) -> Optional[List[SandboxType]]:
return None # any sandbox
async def execute(self, sandbox_context: Any, name: str = 'world', **kwargs) -> Dict[str, Any]:
return {'message': f'Hello, {name}!'}
Enable and call:
import asyncio
from ms_enclave.sandbox.boxes import SandboxFactory
from ms_enclave.sandbox.model import SandboxType, DockerSandboxConfig
async def main():
config = DockerSandboxConfig(
image='python:3.11-slim',
tools_config={'hello': {}},
)
async with SandboxFactory.create_sandbox(SandboxType.DOCKER, config) as sb:
print(await sb.execute_tool('hello', {'name': 'ms-enclave'}))
# {'message': 'Hello, ms-enclave!'}
asyncio.run(main())
Example B: prefer in-sandbox commands¶
Declare required_sandbox_types=[SandboxType.DOCKER] and run commands via sandbox_context.execute_command; fall back to local logic when not available.
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from ms_enclave.sandbox.tools.base import Tool, register_tool
from ms_enclave.sandbox.model import SandboxType
@register_tool('time_teller')
class TimeTellerTool(Tool):
def __init__(self, name: str = 'time_teller', description: str = 'Tell current time', enabled: bool = True):
super().__init__(name=name, description=description, enabled=enabled)
@property
def required_sandbox_types(self) -> Optional[List[SandboxType]]:
return [SandboxType.DOCKER] # DOCKER and its subtypes (e.g. DOCKER_NOTEBOOK)
async def execute(self, sandbox_context: Any, timezone_name: Optional[str] = None, **kwargs) -> Dict[str, Any]:
cmd = f'TZ={timezone_name} date' if timezone_name else 'date'
try:
exit_code, out, err = await sandbox_context.execute_command(cmd, timeout=5)
if exit_code == 0:
return {'time': out.strip()}
return {'error': err.strip() or 'unknown error'}
except Exception:
tz = timezone.utc if (timezone_name or '').upper() == 'UTC' else None
return {'time': datetime.now(tz=tz).isoformat()}
Strict parameter validation¶
For validated / documented parameters when the model calls the tool, pass parameters (a Pydantic model β see tools/tool_info.py) to the Tool constructor. The schema is automatically reflected in the OpenAI function definition.
Skip it when not needed β parameters in the schema becomes {}.