Source code for praval.tools

"""
Tool decorator and utilities for Praval Framework.

This module provides the @tool decorator for creating tools that can be
registered and used by agents. Tools are automatically registered in the
global tool registry and can be associated with specific agents.
"""

import inspect
from typing import Optional, List, Callable, Union, Any
from functools import wraps

from .core.tool_registry import Tool, ToolMetadata, get_tool_registry, ToolRegistry
from .core.exceptions import ToolError


[docs] def tool( tool_name: Optional[str] = None, owned_by: Optional[str] = None, description: Optional[str] = None, category: str = "general", shared: bool = False, version: str = "1.0.0", author: str = "", tags: Optional[List[str]] = None ) -> Callable: """ Decorator to register a function as a tool in the Praval framework. The @tool decorator automatically registers functions as tools that can be used by agents. Tools can be owned by specific agents, shared across all agents, or organized by category. Args: tool_name: Name of the tool (defaults to function name) owned_by: Agent that owns this tool description: Description of what the tool does (defaults to docstring) category: Category for organizing tools shared: Whether this tool is available to all agents version: Version of the tool author: Author of the tool tags: Tags for tool discovery Returns: Decorated function with tool metadata attached Raises: ToolError: If tool registration fails or validation errors occur Examples: Basic tool owned by specific agent: ```python @tool("add_numbers", owned_by="calculator") def add(x: float, y: float) -> float: '''Add two numbers together.''' return x + y ``` Shared tool available to all agents: ```python @tool("logger", shared=True, category="utility") def log_message(level: str, message: str) -> str: '''Log a message at the specified level.''' import logging logger = logging.getLogger("praval.tools") getattr(logger, level.lower())(message) return f"Logged: {message}" ``` Tool with metadata: ```python @tool( "validate_email", owned_by="data_processor", category="validation", tags=["email", "validation", "data"], version="2.0.0", author="Praval Team" ) def validate_email(email: str) -> bool: '''Validate email address format.''' import re pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$' return bool(re.match(pattern, email)) ``` """ def decorator(func: Callable) -> Callable: # Auto-generate tool name from function name if not provided actual_tool_name = tool_name or func.__name__ # Auto-generate description from docstring if not provided actual_description = description if not actual_description and func.__doc__: actual_description = func.__doc__.strip() # Prepare tags list actual_tags = tags or [] # Create tool metadata metadata = ToolMetadata( tool_name=actual_tool_name, owned_by=owned_by, description=actual_description or "", category=category, shared=shared, version=version, author=author, tags=actual_tags ) # Create tool instance try: tool_instance = Tool(func, metadata) except Exception as e: raise ToolError(f"Failed to create tool '{actual_tool_name}': {str(e)}") from e # Register the tool in the global registry registry = get_tool_registry() try: registry.register_tool(tool_instance) except Exception as e: raise ToolError(f"Failed to register tool '{actual_tool_name}': {str(e)}") from e # Add tool metadata to the function for introspection func._praval_tool = tool_instance func._praval_tool_name = actual_tool_name func._praval_tool_metadata = metadata # Add utility methods to the function func.get_metadata = lambda: metadata func.get_tool_info = lambda: tool_instance.to_dict() func.execute_as_tool = tool_instance.execute return func return decorator
[docs] def get_tool_info(tool_func: Callable) -> dict: """ Get information about a @tool decorated function. Args: tool_func: Function decorated with @tool Returns: Dictionary with tool metadata Raises: ValueError: If function is not decorated with @tool """ if not hasattr(tool_func, '_praval_tool'): raise ValueError("Function is not decorated with @tool") return tool_func._praval_tool.to_dict()
[docs] def is_tool(func: Callable) -> bool: """ Check if a function is decorated with @tool. Args: func: Function to check Returns: True if function is a tool, False otherwise """ return hasattr(func, '_praval_tool')
[docs] def discover_tools( module: Optional[str] = None, pattern: Optional[str] = None, category: Optional[str] = None ) -> List[Tool]: """ Discover tools based on various criteria. Args: module: Module name to search for tools pattern: File pattern to search (e.g., "*_tool.py") category: Category to filter by Returns: List of discovered Tool instances """ registry = get_tool_registry() if category: return registry.get_tools_by_category(category) # For module and pattern discovery, we'd need to implement # module introspection and file system scanning # For now, return all tools as a basic implementation return registry.list_all_tools()
[docs] def list_tools( agent_name: Optional[str] = None, category: Optional[str] = None, shared_only: bool = False ) -> List[dict]: """ List tools with optional filtering. Args: agent_name: Filter by agent owner category: Filter by category shared_only: Only show shared tools Returns: List of tool information dictionaries """ registry = get_tool_registry() if agent_name: tools = registry.get_tools_for_agent(agent_name) elif category: tools = registry.get_tools_by_category(category) elif shared_only: tools = registry.get_shared_tools() else: tools = registry.list_all_tools() return [tool.to_dict() for tool in tools]
[docs] def register_tool_with_agent(tool_name: str, agent_name: str) -> bool: """ Register an existing tool with an agent at runtime. Args: tool_name: Name of the tool to register agent_name: Name of the agent to register with Returns: True if registration successful, False otherwise """ registry = get_tool_registry() return registry.assign_tool_to_agent(tool_name, agent_name)
[docs] def unregister_tool_from_agent(tool_name: str, agent_name: str) -> bool: """ Unregister a tool from an agent at runtime. Args: tool_name: Name of the tool to unregister agent_name: Name of the agent to unregister from Returns: True if unregistration successful, False otherwise """ registry = get_tool_registry() return registry.remove_tool_from_agent(tool_name, agent_name)
[docs] class ToolCollection: """ A collection of related tools that can be managed as a group. Useful for organizing tools by functionality or creating tool suites that can be easily assigned to agents. """
[docs] def __init__(self, name: str, description: str = ""): """ Initialize a tool collection. Args: name: Name of the collection description: Description of the collection """ self.name = name self.description = description self.tools: List[str] = []
[docs] def add_tool(self, tool_name: str) -> None: """ Add a tool to the collection. Args: tool_name: Name of the tool to add Raises: ToolError: If tool doesn't exist """ registry = get_tool_registry() if not registry.get_tool(tool_name): raise ToolError(f"Tool '{tool_name}' not found in registry") if tool_name not in self.tools: self.tools.append(tool_name)
[docs] def remove_tool(self, tool_name: str) -> bool: """ Remove a tool from the collection. Args: tool_name: Name of the tool to remove Returns: True if removal successful, False if tool wasn't in collection """ if tool_name in self.tools: self.tools.remove(tool_name) return True return False
[docs] def assign_to_agent(self, agent_name: str) -> int: """ Assign all tools in the collection to an agent. Args: agent_name: Name of the agent to assign tools to Returns: Number of tools successfully assigned """ registry = get_tool_registry() successful = 0 for tool_name in self.tools: if registry.assign_tool_to_agent(tool_name, agent_name): successful += 1 return successful
[docs] def get_tools(self) -> List[Tool]: """ Get all tools in the collection. Returns: List of Tool instances in the collection """ registry = get_tool_registry() result = [] for tool_name in self.tools: tool = registry.get_tool(tool_name) if tool: result.append(tool) return result