Customization
Creating Tools
Learn how to create reusable tools that can be used across multiple nodes.
Tools are reusable utility classes that encapsulate specific functionality. They can be used by multiple nodes to avoid code duplication and maintain consistency. Examples include language model tools, web search tools, and knowledge base tools.
Tool Structure
Tools are typically Python classes that provide a clean interface for specific operations. They should:
- Handle initialization and configuration
- Provide clear, well-documented methods
- Return consistent data structures
- Handle errors gracefully
- Support multiple service providers when applicable
Step-by-Step Guide
Step 1: Create Tool Directory
Create a new directory for your tool in backend/tools/:
bash
backend/tools/
└── my_custom_tool/
├── __init__.py
└── my_custom_tool.pyStep 2: Implement the Tool Class
Create your tool class with the desired functionality:
python
"""
My Custom Tool - Description of what this tool does
"""
import sys
import os
from typing import Dict, Any, Optional
# Add parent directory to path if needed
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
class MyCustomTool:
"""
My Custom Tool - Provides specific functionality.
This tool can be used by multiple nodes to perform
a specific operation without code duplication.
"""
def __init__(self, config: Optional[Dict[str, Any]] = None):
"""
Initialize the tool.
Args:
config: Optional configuration dictionary
"""
self.config = config or {}
self.initialized = False
self._initialize()
def _initialize(self):
"""Initialize the tool (load resources, connect to services, etc.)"""
try:
# Initialize your tool here
# e.g., connect to API, load models, etc.
self.initialized = True
except Exception as e:
print(f"Warning: Could not initialize tool: {e}")
self.initialized = False
def perform_operation(
self,
input_data: str,
**kwargs
) -> Dict[str, Any]:
"""
Perform the main operation.
Args:
input_data: The input data to process
**kwargs: Additional parameters
Returns:
Dictionary containing the result and metadata
"""
if not self.initialized:
return {
"success": False,
"error": "Tool not initialized",
"result": None
}
try:
# Perform your operation here
result = f"Processed: {input_data}"
return {
"success": True,
"result": result,
"metadata": {
"input_length": len(input_data),
"operation": "custom_operation"
}
}
except Exception as e:
return {
"success": False,
"error": str(e),
"result": None
}
def get_status(self) -> Dict[str, Any]:
"""
Get the current status of the tool.
Returns:
Dictionary containing status information
"""
return {
"initialized": self.initialized,
"config": self.config
}
# Convenience function for quick usage
def use_custom_tool(input_data: str, **kwargs) -> str:
"""
Quick function to use the custom tool.
Args:
input_data: The input data
**kwargs: Additional parameters
Returns:
The result as a string, or error message if failed
"""
tool = MyCustomTool()
result = tool.perform_operation(input_data, **kwargs)
if result["success"]:
return result["result"]
else:
return f"Error: {result['error']}"Step 3: Use the Tool in a Node
Import and use your tool in any node:
python
from typing import Dict, Any
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from base_node import BaseNode, NodeInput, NodeOutput, NodeParameter
# Import your tool
try:
from tools.my_custom_tool.my_custom_tool import MyCustomTool
except ImportError:
MyCustomTool = None
class MyNodeUsingTool(BaseNode):
# ... define inputs, outputs, parameters ...
def execute(self, inputs: Dict[str, Any], parameters: Dict[str, Any]) -> Dict[str, Any]:
# Check if tool is available
if MyCustomTool is None:
return {
"result": "",
"success": False,
"metadata": {"error": "Tool not available"}
}
# Initialize the tool
tool = MyCustomTool()
# Use the tool
input_data = inputs.get("input_data", "")
tool_result = tool.perform_operation(input_data)
if tool_result["success"]:
return {
"result": tool_result["result"],
"success": True,
"metadata": tool_result.get("metadata", {})
}
else:
return {
"result": "",
"success": False,
"metadata": {"error": tool_result.get("error", "Unknown error")}
}Example: Language Model Tool
Here's a real example from ConvoFlow - the Language Model Tool that supports multiple providers:
python
class LanguageModelTool:
"""
Tool for using language models across multiple providers.
"""
def __init__(self):
"""Initialize with all available services."""
self.services = {}
# Add available services
if OpenAIService:
try:
self.services["openai"] = OpenAIService()
except Exception as e:
print(f"Warning: Could not initialize OpenAI: {e}")
# Add other services similarly...
def generate_response(
self,
query: str,
service: str = "openai",
model: Optional[str] = None,
**kwargs
) -> Dict[str, Any]:
"""
Generate a response using the specified service.
Returns:
Dictionary with success, response, and metadata
"""
# Implementation...
passBest Practices
- Error Handling: Always return structured error responses
- Initialization: Handle initialization failures gracefully
- Consistency: Use consistent return structures across methods
- Documentation: Document all methods and parameters clearly
- Reusability: Design tools to be reusable across different nodes
- Configuration: Support configuration through environment variables or parameters
Tool vs Node
Understanding when to create a tool vs a node:
Create a Tool when:
- Functionality is reusable across multiple nodes
- You need to encapsulate complex logic
- You want to support multiple service providers
- You need to manage resources (connections, models, etc.)
Create a Node when:
- You need a workflow component
- You want user-configurable parameters
- You need inputs/outputs for workflow connections
- You want visual representation in the workflow
Was this page helpful?