Tool Design Principles
A good Agent tool should follow these principles:
- Single Responsibility: Each tool does one thing and does it well
- Clear Interface: Inputs and outputs are explicit, easy to understand and use
- Robust Error Handling: Gracefully handle various exceptional conditions
- Observability: Provide logs and metrics for debugging and monitoring
- Security: Permission control and auditing for sensitive operations
Tool Interface Design
from pydantic import BaseModel, Field
from typing import Optional, List, Any
from enum import Enum
class ToolCategory(str, Enum):
SEARCH = "search"
DATA = "data"
ACTION = "action"
UTILITY = "utility"
class ToolParameter(BaseModel):
name: str
type: str
description: str
required: bool = True
enum: Optional[List[str]] = None
class ToolDefinition(BaseModel):
name: str = Field(description="Tool name")
description: str = Field(description="Detailed description to help Agent understand when to use")
category: ToolCategory
parameters: List[ToolParameter]
risk_level: str = Field(default="low", pattern="^(low|medium|high|critical)$")
def to_openai_function(self) -> dict:
"""Convert to OpenAI Function Calling format"""
properties = {}
required = []
for param in self.parameters:
prop = {"type": param.type, "description": param.description}
if param.enum:
prop["enum"] = param.enum
properties[param.name] = prop
if param.required:
required.append(param.name)
return {
"name": self.name,
"description": self.description,
"parameters": {
"type": "object",
"properties": properties,
"required": required
}
}Implementing a Complete Tool
import logging
import time
from functools import wraps
logger = logging.getLogger(__name__)
def tool_logger(func):
"""Tool decorator: automatically log call and duration"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
try:
result = func(*args, **kwargs)
elapsed = time.time() - start
logger.info(f"Tool {func.__name__} succeeded in {elapsed:.2f}s")
return result
except Exception as e:
elapsed = time.time() - start
logger.error(f"Tool {func.__name__} failed in {elapsed:.2f}s: {e}")
raise
return wrapper
class DatabaseQueryTool:
"""Database query tool"""
definition = ToolDefinition(
name="query_database",
description="Query database, supports SQL queries. Used to fetch user data, order information, statistics reports, etc.",
category=ToolCategory.DATA,
parameters=[
ToolParameter(
name="query_type",
type="string",
description="Query type",
enum=["user", "order", "stats"]
),
ToolParameter(
name="filters",
type="object",
description="Query filter conditions",
required=False
)
],
risk_level="medium"
)
def __init__(self, db_connection):
self.db = db_connection
self.allowed_tables = ["users", "orders", "products"]
@tool_logger
def execute(self, query_type: str, filters: dict = None):
# Security validation
if query_type not in ["user", "order", "stats"]:
return {"error": f"Unsupported query type: {query_type}"}
# Build safe query
query = self._build_safe_query(query_type, filters or {})
# Execute query
result = self.db.execute(query)
return {
"success": True,
"data": result,
"count": len(result)
}
def _build_safe_query(self, query_type: str, filters: dict):
# Use parameterized queries to prevent SQL injection
# Only allow querying predefined tables
passTool Registration and Discovery
class ToolRegistry:
"""Tool registry"""
def __init__(self):
self._tools: dict[str, Any] = {}
def register(self, tool):
"""Register a tool"""
name = tool.definition.name
if name in self._tools:
raise ValueError(f"Tool {name} already registered")
self._tools[name] = tool
logger.info(f"Registered tool: {name}")
def get_all_definitions(self):
"""Get all tool definitions (for Function Calling)"""
return [
tool.definition.to_openai_function()
for tool in self._tools.values()
]
def execute(self, name: str, params: dict):
"""Execute a tool"""
if name not in self._tools:
return {"error": f"Tool not found: {name}"}
tool = self._tools[name]
# High-risk operations require confirmation
if tool.definition.risk_level in ["high", "critical"]:
if not self._confirm_execution(name, params):
return {"error": "Operation cancelled by user"}
return tool.execute(**params)Tool Testing
import pytest
class TestDatabaseQueryTool:
def setup_method(self):
self.tool = DatabaseQueryTool(MockDB())
def test_valid_query(self):
result = self.tool.execute("user", {"name": "Zhang San"})
assert result["success"] is True
assert "data" in result
def test_invalid_query_type(self):
result = self.tool.execute("delete_all")
assert "error" in result
def test_sql_injection_prevention(self):
result = self.tool.execute("user", {"name": "'; DROP TABLE users; --"})
assert result["success"] is True # Should not execute malicious SQLTool Performance Optimization
- Result Caching: Cache results for queries with identical parameters
- Timeout Control: Set execution timeout for each tool
- Parallel Execution: Independent tool calls can be executed in parallel
- Result Trimming: Results returned to the LLM should be concise, containing only necessary information
Summary
Tools are an extension of Agent capabilities. Good tool design empowers the Agent, while poor tool design hinders it. Investing time in proper tool design and testing is key to building high-quality Agent systems.