Skip to main content

Overview

Custom tools allow you to give Smooth any arbitrary function as a tool. This is similar to MCP (Model Context Protocol) but without having to deal with servers or additional infrastructure. Custom tools can be used for virtually anything, including:
  • Browser interaction - Execute JavaScript to manipulate the DOM or extract data
  • Human-in-the-loop - Ask questions and get input from a human operator
  • Handling OTP scenarios - Retrieve one-time passwords from your email or SMS service
  • Database operations - Add, update, or query data in your local or remote databases
  • API integrations - Call external APIs that require credentials or complex logic
  • File system operations - Read configuration files, process local data, etc.
  • Custom validation - Implement business-specific validation logic

Basic Usage

Use the @client.tool() decorator to register a Python function as a custom tool:
from smooth import SmoothClient

client = SmoothClient(api_key="cmzr-YOUR_API_KEY")

@client.tool(
    name="ask_human",
    description="Asks a human operator for input when you need clarification or additional information.",
    inputs={
        "question": {
            "type": "string",
            "description": "The question to ask the human operator",
        }
    },
    output="string"
)
def ask_human(question: str):
    """
    Prompts a human operator for input and returns their response.
    Useful for human-in-the-loop workflows.
    """
    print(f"\nQuestion for human: {question}")
    response = input("Your answer: ")
    return response

# Use the custom tool in a task
task = client.run(
    task="Ask the human for their favorite book and then find the price on Amazon",
    custom_tools=[ask_human]
)

Browser JavaScript Execution

Within custom tools, you can execute JavaScript directly in the browser context. Your JavaScript code can access to the DOM, browser APIs, and page state. To use this feature, add a task: smooth.TaskHandle parameter to your tool function:
import smooth

client = smooth.SmoothClient(api_key="cmzr-YOUR_API_KEY")

@client.tool(
    name="extract_page_data",
    description="Extracts and analyzes data from the current page using JavaScript",
    inputs={},
    output="any"
)
def extract_page_data(task: smooth.TaskHandle):
    # Execute JavaScript in the browser and return the result
    result = task.exec_js("""
        () => {
            // Full access to DOM and browser APIs
            const data = {
                title: document.title,
                url: window.location.href,
                links: Array.from(document.querySelectorAll('a')).map(a => a.href),
                hasLoginForm: !!document.querySelector('input[type="password"]'),
                visibleText: document.body.innerText.substring(0, 50)
            };
            return data;
        }
    """)

    # Process the result in Python
    return f"Extracted {len(result['links'])} links on {result['title']}"
Use JavaScript for browser-level automation and data extraction, and Python for backend processing and integrations. Combine both for powerful workflows.

Tool Decorator Parameters

The @client.tool() decorator accepts the following parameters:
name
string
required
The name of the tool that the agent will see and use to call it. Use descriptive, clear names.
description
string
required
A clear description of what the tool does and when to use it. The agent uses this to decide when to call your tool.
inputs
dict
required
A dictionary describing the input parameters for the tool. Each key is a parameter name, and the value is an object with:
  • type (string): The data type (e.g., “string”, “number”, “boolean”, “object”, “array”)
  • description (string): A clear description of what this parameter is for
output
string
required
The return type of the tool (e.g., “string”, “number”, “boolean”, “object”, “array”)

Error Handling

Custom tools support two types of error handling:

ToolCallError (Non-Fatal)

Use ToolCallError for expected errors that the agent should handle gracefully. The error message will be sent to the agent, allowing it to retry or adjust its approach.
from smooth import ToolCallError

@client.tool(
    name="validate_code",
    description="Validates a verification code",
    inputs={"code": {"type": "string", "description": "The code to validate"}},
    output="boolean"
)
def validate_code(code: str):
    if not code.isdigit():
        raise ToolCallError("Code must contain only digits")
    if len(code) != 6:
        raise ToolCallError("Code must be exactly 6 digits")
    return True

Fatal Exceptions

Any other exception raised by your code is considered fatal and will immediately interrupt the task execution.
@client.tool(
    name="query_database",
    description="Queries the user database",
    inputs={"user_id": {"type": "string", "description": "User ID to query"}},
    output="object"
)
def query_database(user_id: str):
    try:
        # Database query logic
        result = db.query(user_id)
        return result
    except ConnectionError:
        # This will stop the task immediately
        raise Exception("Database connection failed - critical error")
Tool Descriptions: Write clear, specific descriptions for your tools and inputs. The agent relies on these descriptions to decide when and how to use your tools effectively.

Best Practices

  1. Keep tools simple to use - Each tool should do one thing well
  2. Use descriptive names - Name tools clearly so the agent knows when to use them
  3. Handle errors gracefully - Use ToolCallError for recoverable errors
  4. Validate inputs - Check that inputs are in the expected format before processing
  5. Return meaningful values - Provide clear, actionable responses that help the agent continue
  6. Test independently - Test your tool functions separately before using them in tasks