xml-pipeline/agentserver/tools/base.py
dullfig 986db2e79b Implement native tools and infrastructure
Tools (18 total):
- calculate: Safe AST-based math expression evaluator
- fetch: Async HTTP with SSRF protection
- files: Sandboxed read/write/list/delete
- shell: Command execution with blocklist
- search: Web search (SerpAPI, Google, Bing)
- keyvalue: In-memory key-value store
- librarian: exist-db XML database integration
- convert: XML↔JSON conversion + XPath extraction

Infrastructure:
- CLI with run/init/check/version commands
- Config loader for organism.yaml
- Feature detection for optional dependencies
- Optional extras in pyproject.toml

LLM:
- Fixed llm_connection.py to wrap working router

WASM:
- Documented WASM listener interface
- Stub implementation for future work

MCP:
- Reddit sentiment MCP server example

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 20:25:48 -08:00

115 lines
3 KiB
Python

"""
Base classes and registry for tools.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional
from functools import wraps
import inspect
@dataclass
class ToolResult:
"""Result from a tool invocation."""
success: bool
data: Any = None
error: Optional[str] = None
@dataclass
class Tool:
"""Tool metadata and implementation."""
name: str
description: str
func: Callable
parameters: Dict[str, Any] = field(default_factory=dict)
async def invoke(self, **kwargs) -> ToolResult:
"""Invoke the tool with given parameters."""
try:
result = await self.func(**kwargs)
if isinstance(result, ToolResult):
return result
return ToolResult(success=True, data=result)
except Exception as e:
return ToolResult(success=False, error=str(e))
class ToolRegistry:
"""Registry of available tools."""
def __init__(self):
self._tools: Dict[str, Tool] = {}
def register(self, tool: Tool):
"""Register a tool."""
self._tools[tool.name] = tool
def get(self, name: str) -> Optional[Tool]:
"""Get a tool by name."""
return self._tools.get(name)
def list(self) -> List[str]:
"""List all registered tool names."""
return list(self._tools.keys())
def all(self) -> Dict[str, Tool]:
"""Get all tools."""
return dict(self._tools)
# Global registry
_registry: Optional[ToolRegistry] = None
def get_tool_registry() -> ToolRegistry:
"""Get the global tool registry."""
global _registry
if _registry is None:
_registry = ToolRegistry()
return _registry
def tool(func: Callable) -> Callable:
"""Decorator to register a function as a tool."""
# Extract metadata from function
name = func.__name__
description = func.__doc__ or ""
# Extract parameters from signature
sig = inspect.signature(func)
parameters = {}
for param_name, param in sig.parameters.items():
param_info = {"name": param_name}
if param.annotation != inspect.Parameter.empty:
ann = param.annotation
# Handle both string annotations (from __future__ import annotations) and type objects
if isinstance(ann, str):
param_info["type"] = ann
elif hasattr(ann, "__name__"):
param_info["type"] = ann.__name__
else:
param_info["type"] = str(ann)
if param.default != inspect.Parameter.empty:
param_info["default"] = param.default
parameters[param_name] = param_info
# Create tool
t = Tool(
name=name,
description=description.strip(),
func=func,
parameters=parameters,
)
# Register
get_tool_registry().register(t)
@wraps(func)
async def wrapper(**kwargs):
return await t.invoke(**kwargs)
wrapper._tool = t
return wrapper