suna/backend/core/utils/TOOL_AUTO_GENERATION.md

13 KiB

Tool Auto-Generation Documentation

How Names, Descriptions & Metadata Are Auto-Generated

The tool discovery system automatically generates human-readable metadata when decorators are not provided. This ensures tools work out-of-the-box without requiring manual configuration.


🏷️ Tool Name Generation

Class Name → Display Name

Logic:

  1. Remove "Tool" suffix (e.g., "SandboxFilesTool" → "SandboxFiles")
  2. Insert spaces before capital letters (CamelCase → "Sandbox Files")
  3. Replace underscores with spaces (snake_case → "Sb Files")
  4. Title case the result

Examples:

Class Name Generated Display Name
SandboxFilesTool Sandbox Files
sb_shell_tool Sb Shell
MessageTool Message
BrowserTool Browser
DataProvidersTool Data Providers
sb_image_edit_tool Sb Image Edit

Code Location: tool_discovery.py:_generate_display_name()

def _generate_display_name(self, name: str) -> str:
    # Remove "Tool" suffix
    if name.endswith('_tool'): name = name[:-5]
    if name.endswith('Tool'): name = name[:-4]
    
    # CamelCase: "SandboxFiles" -> "Sandbox Files"
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', name)
    s2 = re.sub('([a-z0-9])([A-Z])', r'\1 \2', s1)
    
    # snake_case: "sandbox_files" -> "sandbox files"
    s3 = s2.replace('_', ' ')
    
    # Title case: "sandbox files" -> "Sandbox Files"
    return s3.title()

📝 Tool Description Generation

Source Priority (Highest to Lowest)

  1. @tool_metadata(description=...) decorator (if provided)
  2. Class docstring (if available)
  3. Generic fallback: "{ClassName} functionality"

Examples:

# Option 1: Using decorator (RECOMMENDED)
@tool_metadata(
    display_name="File Operations",
    description="Create, read, edit, and manage files in the workspace"
)
class SandboxFilesTool(Tool):
    pass

# Option 2: Using class docstring (AUTOMATIC)
class SandboxFilesTool(Tool):
    """Create, read, edit, and manage files in the workspace"""
    pass

# Option 3: No decorator, no docstring (FALLBACK)
class SandboxFilesTool(Tool):
    pass
    # Auto-generates: "SandboxFilesTool functionality"

Code Location: tool_discovery.py:_extract_tool_metadata()

if tool_metadata:
    # Use decorator
    metadata["description"] = tool_metadata.description
else:
    # Use docstring or fallback
    metadata["description"] = (
        tool_class.__doc__.strip() 
        if tool_class.__doc__ 
        else f"{tool_class.__name__} functionality"
    )

🔧 Method Name Generation

Method Name → Display Name

Logic: Same as tool names (snake_case → Title Case)

Examples:

Method Name Generated Display Name
create_file Create File
str_replace Str Replace
execute_command Execute Command
browser_navigate_to Browser Navigate To
web_search Web Search

📄 Method Description Generation

Source Priority (Highest to Lowest)

  1. @method_metadata(description=...) decorator (if provided)
  2. @openapi_schema description (from function.description field)
  3. Generic fallback: "{method_name} function"

Examples:

# Option 1: Using method_metadata decorator (RECOMMENDED)
@method_metadata(
    display_name="Create File",
    description="Create new files with content"
)
@openapi_schema({...})
def create_file(self, path: str, content: str):
    pass

# Option 2: Using openapi_schema description (AUTOMATIC)
@openapi_schema({
    "type": "function",
    "function": {
        "name": "create_file",
        "description": "Create a new file at the specified path",  # <-- Used here
        "parameters": {...}
    }
})
def create_file(self, path: str, content: str):
    pass

# Option 3: No decorators (FALLBACK)
@openapi_schema({...})
def create_file(self, path: str, content: str):
    pass
    # Auto-generates: "create_file function"

Code Location: tool_discovery.py:_extract_tool_metadata()

if method_name in method_metadata:
    # Use @method_metadata decorator
    method_info["description"] = method_metadata[method_name].description
else:
    # Try to extract from @openapi_schema
    if schemas[method_name]:
        schema = schemas[method_name][0].schema
        if 'function' in schema and 'description' in schema['function']:
            method_info["description"] = schema['function']['description']
        else:
            method_info["description"] = f"{method_name} function"

🎨 Icon & Color Generation

Default Behavior: Not auto-generated (optional fields)

If not provided via @tool_metadata, these fields are None:

  • icon: Default icon used in UI (e.g., Wrench)
  • color: Default styling applied

To specify:

@tool_metadata(
    display_name="File Operations",
    description="Manage files",
    icon="FolderOpen",  # Lucide icon name
    color="bg-blue-100 dark:bg-blue-800/50"  # Tailwind classes
)

⚖️ Weight (Sorting) Generation

Default: 100 (if not specified)

Lower weight = Higher priority in UI sorting

Recommended Ranges:

  • Core tools: 10-20
  • Primary tools: 30-50
  • Common tools: 60-80
  • Advanced/Rare: 90-100+

Example:

@tool_metadata(
    display_name="File Operations",
    description="Manage files",
    weight=20  # High priority - shows near top
)
class SandboxFilesTool(Tool):
    pass

@tool_metadata(
    display_name="Advanced Analytics",
    description="Complex data analysis",
    weight=95  # Lower priority - shows near bottom
)
class AdvancedAnalyticsTool(Tool):
    pass

Frontend Sorting:

import { sortToolsByWeight } from './tool-groups';

const sortedTools = sortToolsByWeight(toolsData);
// Returns tools ordered by weight (ascending)

🔒 Core Tool Detection

Default: is_core = False

Core tools cannot be disabled in the UI.

To mark as core:

@tool_metadata(
    display_name="Message Tool",
    description="User communication",
    is_core=True  # Cannot be disabled
)
class MessageTool(Tool):
    pass

Core Methods:

@method_metadata(
    display_name="Ask Question",
    description="Ask user questions",
    is_core=True  # This method is always enabled
)
def ask(self, question: str):
    pass

👁️ Visible in UI

Default:

  • Tool level: visible = False (tools hidden by default)
  • Method level: visible = True (methods visible by default)

Controls whether a tool/method is shown in the frontend UI.

Use Cases:

  • Set to False for internal/system tools
  • Set to False for deprecated features
  • Set to False for tools that should only be used programmatically
  • Set to False for beta features not ready for general use

Tool Level:

@tool_metadata(
    display_name="Internal System Tool",
    description="Internal functionality not shown to users",
    visible=False  # Hidden from UI
)
class InternalTool(Tool):
    pass

@tool_metadata(
    display_name="File Operations",
    description="Standard file operations",
    visible=True  # Visible in UI (this is the default)
)
class SandboxFilesTool(Tool):
    pass

Method Level:

class SandboxFilesTool(Tool):
    
    @method_metadata(
        display_name="Create File",
        description="Create new files",
        visible=True  # Visible in UI (default)
    )
    def create_file(self, path: str):
        pass
    
    @method_metadata(
        display_name="Internal Helper",
        description="Internal method not shown in UI",
        visible=False  # Hidden from UI
    )
    def internal_helper(self, path: str):
        pass

Difference from is_core:

  • is_core=True: Always visible AND cannot be disabled
  • visible=False: Hidden from UI entirely (users never see it)

📊 Complete Example

from core.agentpress.tool import Tool, tool_metadata, method_metadata, openapi_schema

# Full manual control (BEST for production)
@tool_metadata(
    display_name="File Operations",
    description="Create, read, edit, and manage files in your workspace",
    icon="FolderOpen",
    color="bg-blue-100 dark:bg-blue-800/50",
    is_core=False,
    weight=20,  # Show near top
    visible=True  # Visible in UI (default)
)
class SandboxFilesTool(Tool):
    
    @method_metadata(
        display_name="Create File",
        description="Create a new file with specified content",
        is_core=False,
        visible=True  # Visible in UI (default)
    )
    @openapi_schema({
        "type": "function",
        "function": {
            "name": "create_file",
            "description": "Create a new file at the given path",
            "parameters": {...}
        }
    })
    def create_file(self, path: str, content: str):
        return self.success_response("File created!")
    
    # This method auto-generates display name & description from schema
    @openapi_schema({
        "type": "function",
        "function": {
            "name": "delete_file",
            "description": "Delete a file from the workspace",  # <-- Auto-used
            "parameters": {...}
        }
    })
    def delete_file(self, path: str):
        return self.success_response("File deleted!")
    
    # Internal helper method - hidden from UI
    @method_metadata(
        display_name="Internal Validation",
        description="Internal validation logic not shown to users",
        visible=False  # Hidden from UI
    )
    @openapi_schema({...})
    def _internal_validate(self, path: str):
        return self.success_response("Validated!")

Generated Metadata:

{
  "name": "sb_files_tool",
  "display_name": "File Operations",
  "description": "Create, read, edit, and manage files in your workspace",
  "icon": "FolderOpen",
  "color": "bg-blue-100 dark:bg-blue-800/50",
  "is_core": false,
  "weight": 20,
  "visible": true,
  "methods": [
    {
      "name": "create_file",
      "display_name": "Create File",
      "description": "Create a new file with specified content",
      "is_core": false,
      "visible": true
    },
    {
      "name": "delete_file",
      "display_name": "Delete File",  // Auto-generated from method name
      "description": "Delete a file from the workspace",  // From schema
      "is_core": false,
      "visible": true  // Auto-default
    },
    {
      "name": "_internal_validate",
      "display_name": "Internal Validation",
      "description": "Internal validation logic not shown to users",
      "is_core": false,
      "visible": false  // Hidden from UI
    }
  ]
}

🎯 Best Practices

DO

  1. Use @tool_metadata for all public tools

    @tool_metadata(display_name="...", description="...", weight=20)
    
  2. Use @method_metadata for important methods

    @method_metadata(display_name="...", description="...")
    
  3. Put good descriptions in @openapi_schema

    @openapi_schema({
        "function": {"description": "Clear, helpful description"}
    })
    
  4. Use class docstrings as fallback

    class MyTool(Tool):
        """This is a good description"""
    

DON'T

  1. Don't leave tools without any metadata - At least add a docstring
  2. Don't use generic method names without metadata - "do_thing" is unclear
  3. Don't forget to set weight - Tools will appear in random order
  4. Don't mark everything as core - Only essential tools should be core

🔍 Summary

Field Source 1 (Best) Source 2 (Good) Source 3 (Fallback)
Tool Name @tool_metadata(display_name=...) - Auto from class name
Tool Description @tool_metadata(description=...) Class docstring "{ClassName} functionality"
Tool Icon @tool_metadata(icon=...) - None (UI default)
Tool Color @tool_metadata(color=...) - None (UI default)
Tool Weight @tool_metadata(weight=...) - 100
Tool Visible @tool_metadata(visible=...) - True
Tool is_core @tool_metadata(is_core=True) - False
Method Name @method_metadata(display_name=...) - Auto from method name
Method Description @method_metadata(description=...) @openapi_schema description "{method_name} function"
Method Visible @method_metadata(visible=...) - True
Method is_core @method_metadata(is_core=True) - False

The system gracefully degrades - even without ANY decorators, tools still work with auto-generated names! 🎉