snapkitty
compilers
c
python
nasm
sovereign-engine-v2 / src /tools /registry.py
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/sovereign-engine-v2
9abace2 verified
Raw History Blame Contribute Delete
14.1 kB
"""
Universal Tool Registry with Risk Classification
Part of SOVEREIGN PYTHON LLM ENGINE
Features:
- Tool registration and discovery
- Risk classification (0-8 scale)
- Approval policy enforcement
- JSONSchema validation
- WORM ledger integration
"""
from dataclasses import dataclass, field
from enum import IntEnum
from typing import Any, Callable, Awaitable
import jsonschema
# ==========================================
# Risk Classification
# ==========================================
class RiskClass(IntEnum):
"""
Tool risk classification.
0-2: Auto-approve (read-only, pure computation)
3-4: User confirmation (writes with undo)
5-6: Explicit approval (destructive operations)
7-8: Admin-only (infrastructure, financial, legal)
"""
PURE_COMPUTATION = 0 # Math, logic, no I/O
READ_ONLY_LOCAL = 1 # Read local files
READ_ONLY_REMOTE = 2 # Read web/APIs
REVERSIBLE_LOCAL_WRITE = 3 # Write files (backups exist)
REVERSIBLE_REMOTE_WRITE = 4 # API writes (can be undone)
DESTRUCTIVE_LOCAL = 5 # Delete files, no undo
DESTRUCTIVE_REMOTE = 6 # Delete remote resources
PRIVILEGED_INFRASTRUCTURE = 7 # Deploy, config changes
FINANCIAL_OR_LEGAL = 8 # Payments, contracts
# ==========================================
# Approval Policy
# ==========================================
class ApprovalPolicy(IntEnum):
"""
Approval policy for tool execution.
"""
AUTOMATIC = 0 # No approval needed
USER_CONFIRMATION = 1 # Ask user before execution
ADMIN_ONLY = 2 # Require admin privileges
NEVER = 3 # Tool is disabled
# ==========================================
# Tool Definition
# ==========================================
@dataclass
class ToolDefinition:
"""
Complete tool definition with metadata and handler.
Fields:
tool_id: Unique identifier (e.g., "embeddings.encode_text")
version: Semantic version (e.g., "1.0.0")
title: Human-readable title
description: Tool description (used in prompts)
input_schema: JSONSchema for input parameters
output_schema: JSONSchema for output (optional)
risk_class: Risk classification (0-8)
approval_policy: Approval policy
sandbox_required: Whether to run in sandbox
timeout_ms: Execution timeout in milliseconds
handler: Async function that executes the tool
tags: Optional tags for categorization
"""
tool_id: str
version: str
title: str
description: str
input_schema: dict[str, Any]
output_schema: dict[str, Any] | None
risk_class: RiskClass
approval_policy: ApprovalPolicy
sandbox_required: bool
timeout_ms: int
handler: Callable[[dict[str, Any]], Awaitable[dict[str, Any]]]
tags: list[str] = field(default_factory=list)
def validate_input(self, params: dict[str, Any]) -> tuple[bool, str | None]:
"""
Validate input parameters against schema.
Args:
params: Input parameters
Returns:
(is_valid, error_message) tuple
"""
try:
jsonschema.validate(instance=params, schema=self.input_schema)
return True, None
except jsonschema.ValidationError as e:
return False, str(e)
def validate_output(self, result: dict[str, Any]) -> tuple[bool, str | None]:
"""
Validate output against schema.
Args:
result: Output result
Returns:
(is_valid, error_message) tuple
"""
if self.output_schema is None:
return True, None
try:
jsonschema.validate(instance=result, schema=self.output_schema)
return True, None
except jsonschema.ValidationError as e:
return False, str(e)
def to_dict(self) -> dict[str, Any]:
"""
Convert to dictionary (for serialization).
Note: handler is excluded (not serializable)
"""
return {
'tool_id': self.tool_id,
'version': self.version,
'title': self.title,
'description': self.description,
'input_schema': self.input_schema,
'output_schema': self.output_schema,
'risk_class': self.risk_class.value,
'approval_policy': self.approval_policy.value,
'sandbox_required': self.sandbox_required,
'timeout_ms': self.timeout_ms,
'tags': self.tags
}
# ==========================================
# Tool Registry
# ==========================================
class ToolRegistry:
"""
Central registry for all tools.
Features:
- Register/unregister tools
- Lookup by ID, namespace, risk class, tags
- Validate tools before registration
"""
def __init__(self):
"""Initialize empty registry"""
self.tools: dict[str, ToolDefinition] = {}
def register(self, tool: ToolDefinition) -> None:
"""
Register a tool.
Args:
tool: Tool definition
Raises:
ValueError: If tool_id already exists or tool is invalid
"""
if tool.tool_id in self.tools:
raise ValueError(f"Tool already registered: {tool.tool_id}")
# Validate tool
if not tool.tool_id:
raise ValueError("tool_id cannot be empty")
if not tool.handler:
raise ValueError("handler is required")
if '.' not in tool.tool_id:
raise ValueError("tool_id must include namespace (e.g., 'embeddings.encode')")
# Register
self.tools[tool.tool_id] = tool
def unregister(self, tool_id: str) -> None:
"""
Unregister a tool.
Args:
tool_id: Tool identifier
Raises:
KeyError: If tool not found
"""
if tool_id not in self.tools:
raise KeyError(f"Tool not found: {tool_id}")
del self.tools[tool_id]
def get(self, tool_id: str) -> ToolDefinition | None:
"""
Get tool by ID.
Args:
tool_id: Tool identifier
Returns:
ToolDefinition if found, None otherwise
"""
return self.tools.get(tool_id)
def has(self, tool_id: str) -> bool:
"""
Check if tool is registered.
Args:
tool_id: Tool identifier
Returns:
True if tool exists
"""
return tool_id in self.tools
def list_all(self) -> list[ToolDefinition]:
"""
List all registered tools.
Returns:
List of all tools
"""
return list(self.tools.values())
def list_by_namespace(self, namespace: str) -> list[ToolDefinition]:
"""
List all tools in namespace.
Args:
namespace: Namespace (e.g., "embeddings")
Returns:
List of tools in namespace
"""
prefix = f"{namespace}."
return [
tool for tool in self.tools.values()
if tool.tool_id.startswith(prefix)
]
def list_by_risk_class(
self,
max_risk: RiskClass,
min_risk: RiskClass = RiskClass.PURE_COMPUTATION
) -> list[ToolDefinition]:
"""
List tools by risk class range.
Args:
max_risk: Maximum risk class (inclusive)
min_risk: Minimum risk class (inclusive)
Returns:
List of tools in risk range
"""
return [
tool for tool in self.tools.values()
if min_risk <= tool.risk_class <= max_risk
]
def list_by_tags(self, tags: list[str], match_all: bool = False) -> list[ToolDefinition]:
"""
List tools by tags.
Args:
tags: Tags to filter by
match_all: If True, tool must have all tags (AND), else any tag (OR)
Returns:
List of matching tools
"""
if match_all:
return [
tool for tool in self.tools.values()
if all(tag in tool.tags for tag in tags)
]
else:
return [
tool for tool in self.tools.values()
if any(tag in tool.tags for tag in tags)
]
def list_namespaces(self) -> list[str]:
"""
List all unique namespaces.
Returns:
List of namespace strings
"""
namespaces = set()
for tool_id in self.tools:
namespace = tool_id.split('.')[0]
namespaces.add(namespace)
return sorted(namespaces)
def search(self, query: str) -> list[ToolDefinition]:
"""
Search tools by query string.
Searches in tool_id, title, description, tags.
Args:
query: Search query
Returns:
List of matching tools
"""
query_lower = query.lower()
results = []
for tool in self.tools.values():
# Check tool_id
if query_lower in tool.tool_id.lower():
results.append(tool)
continue
# Check title
if query_lower in tool.title.lower():
results.append(tool)
continue
# Check description
if query_lower in tool.description.lower():
results.append(tool)
continue
# Check tags
if any(query_lower in tag.lower() for tag in tool.tags):
results.append(tool)
continue
return results
def get_stats(self) -> dict[str, Any]:
"""
Get registry statistics.
Returns:
Dictionary with statistics
"""
risk_distribution = {}
for tool in self.tools.values():
risk_class = tool.risk_class.name
risk_distribution[risk_class] = risk_distribution.get(risk_class, 0) + 1
approval_distribution = {}
for tool in self.tools.values():
approval_policy = tool.approval_policy.name
approval_distribution[approval_policy] = approval_distribution.get(approval_policy, 0) + 1
return {
'total_tools': len(self.tools),
'namespaces': self.list_namespaces(),
'risk_distribution': risk_distribution,
'approval_distribution': approval_distribution,
'sandbox_required_count': sum(1 for t in self.tools.values() if t.sandbox_required)
}
def export_catalog(self) -> dict[str, Any]:
"""
Export tool catalog (for documentation).
Returns:
Dictionary with all tool definitions
"""
return {
'version': '1.0.0',
'tools': [tool.to_dict() for tool in self.tools.values()],
'stats': self.get_stats()
}
# ==========================================
# Global Registry Instance
# ==========================================
# Global registry (singleton pattern)
_global_registry: ToolRegistry | None = None
def get_global_registry() -> ToolRegistry:
"""
Get global tool registry.
Returns:
Global ToolRegistry instance
"""
global _global_registry
if _global_registry is None:
_global_registry = ToolRegistry()
return _global_registry
def register_tool(tool: ToolDefinition) -> None:
"""
Register tool in global registry.
Args:
tool: Tool definition
"""
registry = get_global_registry()
registry.register(tool)
def get_tool(tool_id: str) -> ToolDefinition | None:
"""
Get tool from global registry.
Args:
tool_id: Tool identifier
Returns:
ToolDefinition if found, None otherwise
"""
registry = get_global_registry()
return registry.get(tool_id)
# ==========================================
# Tool Decorator
# ==========================================
def tool(
tool_id: str,
version: str = "1.0.0",
title: str = "",
description: str = "",
input_schema: dict[str, Any] | None = None,
output_schema: dict[str, Any] | None = None,
risk_class: RiskClass = RiskClass.PURE_COMPUTATION,
approval_policy: ApprovalPolicy = ApprovalPolicy.AUTOMATIC,
sandbox_required: bool = False,
timeout_ms: int = 30000,
tags: list[str] | None = None
):
"""
Decorator for registering tools.
Example:
@tool(
tool_id="math.add",
description="Add two numbers",
input_schema={"type": "object", "properties": {"a": {"type": "number"}, "b": {"type": "number"}}},
risk_class=RiskClass.PURE_COMPUTATION
)
async def add_numbers(params: dict[str, Any]) -> dict[str, Any]:
return {"result": params["a"] + params["b"]}
"""
def decorator(func: Callable[[dict[str, Any]], Awaitable[dict[str, Any]]]):
tool_def = ToolDefinition(
tool_id=tool_id,
version=version,
title=title or tool_id,
description=description,
input_schema=input_schema or {},
output_schema=output_schema,
risk_class=risk_class,
approval_policy=approval_policy,
sandbox_required=sandbox_required,
timeout_ms=timeout_ms,
handler=func,
tags=tags or []
)
register_tool(tool_def)
return func
return decorator