tiphys.tools.base

Tool definitions and base classes.

Provides the foundation for defining agent tools with Pydantic schemas.

  1"""
  2Tool definitions and base classes.
  3
  4Provides the foundation for defining agent tools with Pydantic schemas.
  5"""
  6
  7from collections.abc import Awaitable, Callable
  8from functools import wraps
  9from typing import Any, TypeVar
 10
 11from pydantic import BaseModel, Field, create_model
 12
 13# Type variable for handler functions
 14T = TypeVar("T", bound=Callable[..., Awaitable[str]])
 15
 16
 17class ToolDefinition(BaseModel):
 18    """Definition of an agent tool."""
 19
 20    name: str
 21    description: str
 22    args_schema: type[BaseModel]
 23    handler: Callable[..., Awaitable[str]]
 24    requires_permission: bool = False
 25    optional: bool = False
 26
 27    class Config:
 28        arbitrary_types_allowed = True
 29
 30
 31class ToolRegistry:
 32    """Registry for agent tools."""
 33
 34    def __init__(self) -> None:
 35        """Initialize the tool registry."""
 36        self._tools: dict[str, ToolDefinition] = {}
 37
 38    def register(self, tool: ToolDefinition) -> None:
 39        """Register a tool."""
 40        self._tools[tool.name] = tool
 41
 42    def unregister(self, name: str) -> bool:
 43        """Unregister a tool. Returns True if removed."""
 44        if name in self._tools:
 45            del self._tools[name]
 46            return True
 47        return False
 48
 49    def get(self, name: str) -> ToolDefinition | None:
 50        """Get a tool by name."""
 51        return self._tools.get(name)
 52
 53    def get_all(self) -> dict[str, ToolDefinition]:
 54        """Get all registered tools."""
 55        return self._tools.copy()
 56
 57    def list_names(self) -> list[str]:
 58        """List all tool names."""
 59        return list(self._tools.keys())
 60
 61    def clear(self) -> int:
 62        """Clear all tools. Returns count cleared."""
 63        count = len(self._tools)
 64        self._tools.clear()
 65        return count
 66
 67
 68def tool(
 69    name: str,
 70    description: str,
 71    requires_permission: bool = False,
 72) -> Callable[[T], T]:
 73    """
 74    Decorator to define an agent tool.
 75
 76    Automatically extracts args schema from type hints.
 77
 78    Usage:
 79        @tool(name="read_file", description="Read a file")
 80        async def read_file(path: str) -> str:
 81            ...
 82    """
 83
 84    def decorator(func: T) -> T:
 85        # Extract type hints for arguments
 86        import inspect
 87        from typing import get_type_hints
 88
 89        hints = get_type_hints(func)
 90        hints.pop("return", None)
 91
 92        # Build Pydantic model from hints
 93        fields: dict[str, Any] = {}
 94        sig = inspect.signature(func)
 95
 96        for param_name, param in sig.parameters.items():
 97            if param_name in ("context", "self"):
 98                continue
 99
100            param_type = hints.get(param_name, str)
101
102            # Get description from docstring if available
103            param_desc = ""
104
105            # Check if parameter has default
106            if param.default is inspect.Parameter.empty:
107                fields[param_name] = (param_type, Field(..., description=param_desc))
108            else:
109                fields[param_name] = (
110                    param_type,
111                    Field(default=param.default, description=param_desc),
112                )
113
114        args_schema = create_model(f"{name.title().replace('_', '')}Args", **fields)
115
116        # Create tool definition
117        tool_def = ToolDefinition(
118            name=name,
119            description=description,
120            args_schema=args_schema,
121            handler=func,
122            requires_permission=requires_permission,
123        )
124
125        # Attach to function
126        func._tool_definition = tool_def  # type: ignore[attr-defined]
127
128        @wraps(func)
129        async def wrapper(*args: Any, **kwargs: Any) -> str:
130            return await func(*args, **kwargs)
131
132        wrapper._tool_definition = tool_def  # type: ignore[attr-defined]
133        return wrapper  # type: ignore[return-value]
134
135    return decorator
136
137
138def get_tool_definition(func: Callable[..., Any]) -> ToolDefinition | None:
139    """Get the tool definition from a decorated function."""
140    return getattr(func, "_tool_definition", None)
141
142
143# Global tool registry (deprecated - use AppContext)
144_tool_registry: ToolRegistry | None = None
145
146
147def get_tool_registry() -> ToolRegistry:
148    """
149    Get the global tool registry.
150
151    .. deprecated::
152        Use AppContext.tool_registry instead. This function will be removed
153        in a future version.
154
155    Returns:
156        The global ToolRegistry instance.
157    """
158    import warnings
159
160    from tiphys.context import get_context, has_context
161
162    # If AppContext is available, use it (preferred path)
163    if has_context():
164        warnings.warn(
165            "get_tool_registry() is deprecated. Use AppContext.tool_registry instead.",
166            DeprecationWarning,
167            stacklevel=2,
168        )
169        return get_context().tool_registry
170
171    # Fallback to global singleton for backwards compatibility
172    global _tool_registry
173    if _tool_registry is None:
174        _tool_registry = ToolRegistry()
175    return _tool_registry
class ToolDefinition(pydantic.main.BaseModel):
18class ToolDefinition(BaseModel):
19    """Definition of an agent tool."""
20
21    name: str
22    description: str
23    args_schema: type[BaseModel]
24    handler: Callable[..., Awaitable[str]]
25    requires_permission: bool = False
26    optional: bool = False
27
28    class Config:
29        arbitrary_types_allowed = True

Definition of an agent tool.

name: str = PydanticUndefined
description: str = PydanticUndefined
args_schema: type[pydantic.main.BaseModel] = PydanticUndefined
handler: Callable[..., Awaitable[str]] = PydanticUndefined
requires_permission: bool = False
optional: bool = False
class ToolDefinition.Config:
28    class Config:
29        arbitrary_types_allowed = True
arbitrary_types_allowed = True
class ToolRegistry:
32class ToolRegistry:
33    """Registry for agent tools."""
34
35    def __init__(self) -> None:
36        """Initialize the tool registry."""
37        self._tools: dict[str, ToolDefinition] = {}
38
39    def register(self, tool: ToolDefinition) -> None:
40        """Register a tool."""
41        self._tools[tool.name] = tool
42
43    def unregister(self, name: str) -> bool:
44        """Unregister a tool. Returns True if removed."""
45        if name in self._tools:
46            del self._tools[name]
47            return True
48        return False
49
50    def get(self, name: str) -> ToolDefinition | None:
51        """Get a tool by name."""
52        return self._tools.get(name)
53
54    def get_all(self) -> dict[str, ToolDefinition]:
55        """Get all registered tools."""
56        return self._tools.copy()
57
58    def list_names(self) -> list[str]:
59        """List all tool names."""
60        return list(self._tools.keys())
61
62    def clear(self) -> int:
63        """Clear all tools. Returns count cleared."""
64        count = len(self._tools)
65        self._tools.clear()
66        return count

Registry for agent tools.

ToolRegistry()
35    def __init__(self) -> None:
36        """Initialize the tool registry."""
37        self._tools: dict[str, ToolDefinition] = {}

Initialize the tool registry.

def register(self, tool: ToolDefinition) -> None:
39    def register(self, tool: ToolDefinition) -> None:
40        """Register a tool."""
41        self._tools[tool.name] = tool

Register a tool.

def unregister(self, name: str) -> bool:
43    def unregister(self, name: str) -> bool:
44        """Unregister a tool. Returns True if removed."""
45        if name in self._tools:
46            del self._tools[name]
47            return True
48        return False

Unregister a tool. Returns True if removed.

def get(self, name: str) -> ToolDefinition | None:
50    def get(self, name: str) -> ToolDefinition | None:
51        """Get a tool by name."""
52        return self._tools.get(name)

Get a tool by name.

def get_all(self) -> dict[str, ToolDefinition]:
54    def get_all(self) -> dict[str, ToolDefinition]:
55        """Get all registered tools."""
56        return self._tools.copy()

Get all registered tools.

def list_names(self) -> list[str]:
58    def list_names(self) -> list[str]:
59        """List all tool names."""
60        return list(self._tools.keys())

List all tool names.

def clear(self) -> int:
62    def clear(self) -> int:
63        """Clear all tools. Returns count cleared."""
64        count = len(self._tools)
65        self._tools.clear()
66        return count

Clear all tools. Returns count cleared.

def tool( name: str, description: str, requires_permission: bool = False) -> Callable[[~T], ~T]:
 69def tool(
 70    name: str,
 71    description: str,
 72    requires_permission: bool = False,
 73) -> Callable[[T], T]:
 74    """
 75    Decorator to define an agent tool.
 76
 77    Automatically extracts args schema from type hints.
 78
 79    Usage:
 80        @tool(name="read_file", description="Read a file")
 81        async def read_file(path: str) -> str:
 82            ...
 83    """
 84
 85    def decorator(func: T) -> T:
 86        # Extract type hints for arguments
 87        import inspect
 88        from typing import get_type_hints
 89
 90        hints = get_type_hints(func)
 91        hints.pop("return", None)
 92
 93        # Build Pydantic model from hints
 94        fields: dict[str, Any] = {}
 95        sig = inspect.signature(func)
 96
 97        for param_name, param in sig.parameters.items():
 98            if param_name in ("context", "self"):
 99                continue
100
101            param_type = hints.get(param_name, str)
102
103            # Get description from docstring if available
104            param_desc = ""
105
106            # Check if parameter has default
107            if param.default is inspect.Parameter.empty:
108                fields[param_name] = (param_type, Field(..., description=param_desc))
109            else:
110                fields[param_name] = (
111                    param_type,
112                    Field(default=param.default, description=param_desc),
113                )
114
115        args_schema = create_model(f"{name.title().replace('_', '')}Args", **fields)
116
117        # Create tool definition
118        tool_def = ToolDefinition(
119            name=name,
120            description=description,
121            args_schema=args_schema,
122            handler=func,
123            requires_permission=requires_permission,
124        )
125
126        # Attach to function
127        func._tool_definition = tool_def  # type: ignore[attr-defined]
128
129        @wraps(func)
130        async def wrapper(*args: Any, **kwargs: Any) -> str:
131            return await func(*args, **kwargs)
132
133        wrapper._tool_definition = tool_def  # type: ignore[attr-defined]
134        return wrapper  # type: ignore[return-value]
135
136    return decorator

Decorator to define an agent tool.

Automatically extracts args schema from type hints.

Usage: @tool(name="read_file", description="Read a file") async def read_file(path: str) -> str: ...

def get_tool_definition( func: Callable[..., typing.Any]) -> ToolDefinition | None:
139def get_tool_definition(func: Callable[..., Any]) -> ToolDefinition | None:
140    """Get the tool definition from a decorated function."""
141    return getattr(func, "_tool_definition", None)

Get the tool definition from a decorated function.

def get_tool_registry() -> ToolRegistry:
148def get_tool_registry() -> ToolRegistry:
149    """
150    Get the global tool registry.
151
152    .. deprecated::
153        Use AppContext.tool_registry instead. This function will be removed
154        in a future version.
155
156    Returns:
157        The global ToolRegistry instance.
158    """
159    import warnings
160
161    from tiphys.context import get_context, has_context
162
163    # If AppContext is available, use it (preferred path)
164    if has_context():
165        warnings.warn(
166            "get_tool_registry() is deprecated. Use AppContext.tool_registry instead.",
167            DeprecationWarning,
168            stacklevel=2,
169        )
170        return get_context().tool_registry
171
172    # Fallback to global singleton for backwards compatibility
173    global _tool_registry
174    if _tool_registry is None:
175        _tool_registry = ToolRegistry()
176    return _tool_registry

Get the global tool registry.

Deprecated since version : Use AppContext.tool_registry instead. This function will be removed in a future version.

Returns: The global ToolRegistry instance.