Custom tools are functions that extend your AI agent’s capabilities beyond the built-in functionality. They allow agents to interact with external systems, perform specialized operations, and execute complex workflows. What do they do?
Execute specific business logic or API calls
Interact with databases, file systems, or external services
Perform data processing, calculations, or transformations
Enable human-in-the-loop workflows with confirmation and input requirements
Provide caching and performance optimization capabilities
What are the parts of a custom tool?
Function Definition: The core logic that performs the actual work
Type Hints: Required parameter and return type annotations for the LLM to understand usage
Documentation: Clear docstrings explaining the tool’s purpose and parameters
Configuration: Optional behavioral settings like confirmation requirements, caching, or external execution
Error Handling: Robust error management for production reliability
The function definition is the foundation of your custom tool. Follow these guidelines:
Single Responsibility: Each tool should do one thing well
Type Annotations: Every parameter and return value must have type hints
Clear Naming: Use descriptive function names that indicate the tool’s purpose
Documentation: Write comprehensive docstrings that explain parameters, behavior, and return values
Good Tool Definition:
Copy
@toolasync def analyze_website_content(url: str, analysis_type: str = "general") -> Dict[str, Any]: """ Analyzes the content of a website and provides insights based on the specified analysis type. Args: url: The website URL to analyze (must be a valid HTTP/HTTPS URL) analysis_type: Type of analysis to perform ('general', 'seo', 'accessibility', 'performance') Returns: Dictionary containing analysis results with keys: 'status', 'insights', 'recommendations' Raises: ValueError: If the URL is invalid or unreachable ConnectionError: If the website cannot be accessed """ # Tool implementation here pass
Bad Tool Definition:
Copy
@tooldef analyze(url, type): # Missing type hints, docstring, and error handling pass
@toolasync def validated_tool(url: str, timeout: int = 30) -> str: """A tool with input validation.""" if not url.startswith(('http://', 'https://')): raise ValueError("URL must start with http:// or https://") if timeout <= 0 or timeout > 300: raise ValueError("Timeout must be between 1 and 300 seconds") # Tool logic here return "Validated and processed"
@toolasync def well_documented_tool(param: str, option: bool = True) -> Dict[str, Any]: """ Clear description of what the tool does. Args: param: Description of the parameter and its expected format option: Description of the optional parameter and its default behavior Returns: Description of the return value structure and expected keys Raises: ValueError: When param is invalid ConnectionError: When external service is unavailable """ # Tool implementation pass
Let’s Create Custom Tools for a Website Analysis Agent
In this example, we’ll create basic tools and show how to add user confirmation for sensitive operations.
Copy
# Upsonic Docs: Create Custom Tools# https://docs.upsonic.ai/guides/create_custom_tools# Importsfrom upsonic import Agent, Taskfrom upsonic.tools import toolfrom typing import Dict, List, Anyimport requestsfrom bs4 import BeautifulSoupimport time# Tool 1: Basic Website Content Fetcher@toolasync def fetch_website_content(url: str) -> Dict[str, Any]: """ Fetches and returns the HTML content of a website. Args: url: The website URL to fetch content from Returns: Dictionary containing 'status', 'content', 'headers', and 'response_time' Raises: requests.RequestException: If the request fails ValueError: If the URL is invalid """ start_time = time.time() try: response = requests.get(url, timeout=30) response.raise_for_status() return { "status": "success", "content": response.text, "headers": dict(response.headers), "response_time": time.time() - start_time } except requests.RequestException as e: return { "status": "error", "error": str(e), "response_time": time.time() - start_time }# Tool 2: SEO Analysis Tool with Human-in-the-Loop via Hooksfrom upsonic.tools.tool import ToolHooksdef before_analysis_hook(html_content: str): """Hook that runs before SEO analysis to get user confirmation.""" print(f"🔍 About to start SEO analysis on content ({len(html_content)} characters)") # Ask user for confirmation user_input = input("Do you want to proceed with SEO analysis? (y/n): ").lower().strip() if user_input != 'y': raise Exception("Not allowed, rejected by user") print("📊 User approved. Starting SEO analysis...")def after_analysis_hook(result): """Hook that runs after SEO analysis - currently not used.""" pass@tool(tool_hooks=ToolHooks(before=before_analysis_hook, after=after_analysis_hook))async def analyze_seo_metrics(html_content: str) -> Dict[str, Any]: """ Analyzes HTML content for SEO metrics with human-in-the-loop hooks. Args: html_content: The HTML content to analyze Returns: Dictionary containing enhanced SEO metrics, recommendations, and priority actions """ soup = BeautifulSoup(html_content, 'html.parser') # Extract SEO elements title = soup.find('title') meta_description = soup.find('meta', attrs={'name': 'description'}) h1_tags = soup.find_all('h1') seo_score = 0 recommendations = [] # Analyze title if title and title.text.strip(): seo_score += 30 if len(title.text) > 60: recommendations.append("Title is too long (should be under 60 characters)") else: recommendations.append("Missing title tag") # Analyze meta description if meta_description and meta_description.get('content'): seo_score += 30 else: recommendations.append("Missing meta description") # Analyze H1 tags if h1_tags: seo_score += 40 if len(h1_tags) > 1: recommendations.append("Multiple H1 tags found (should have only one)") else: recommendations.append("Missing H1 tag") return { "seo_score": seo_score, "title": title.text.strip() if title else None, "meta_description": meta_description.get('content') if meta_description else None, "h1_count": len(h1_tags), "recommendations": recommendations }# Agent Creation with Custom Toolswebsite_analyzer_agent = Agent( name="Website Analysis Expert", role="Website Performance and SEO Specialist", goal="Provide comprehensive website analysis with actionable insights for optimization")# Example Task Using Custom Toolsanalysis_task = Task( description="Analyze the website 'example.com' for SEO optimization", tools=[ fetch_website_content, analyze_seo_metrics ])# Execute the analysisresult = website_analyzer_agent.do(analysis_task)print("Analysis Complete:", result)
Need more advanced features? The @tool decorator supports many powerful configuration options including:
User Confirmation: Require manual approval before executing sensitive tools
User Input Collection: Prompt users for specific values during execution
External Execution: Pause tool execution for external processes
Result Caching: Cache expensive operations with TTL control
Result Display: Show tool outputs directly to users
Execution Control: Stop agent execution after specific tools
For detailed examples and advanced patterns, see our comprehensive Tools Documentation.