Back to Blog
5 min read

Tool Use Patterns for AI Agents: Effective Function Design

Effective tool design is critical for AI agent success. Today, I will cover patterns for designing and implementing tools that work well with LLMs.

Tool Design Principles

tool_design_principles = {
    "single_responsibility": {
        "description": "Each tool does one thing well",
        "good": "search_documents(query) -> returns documents",
        "bad": "search_and_summarize_documents(query) -> searches AND summarizes"
    },
    "clear_descriptions": {
        "description": "LLM must understand when to use the tool",
        "good": "Search the product catalog by name, category, or price range",
        "bad": "Search products"
    },
    "explicit_parameters": {
        "description": "All inputs clearly defined with types and constraints",
        "good": {"type": "integer", "minimum": 1, "maximum": 100},
        "bad": {"type": "number"}
    },
    "predictable_outputs": {
        "description": "Consistent return structure",
        "good": {"success": True, "data": [...], "total": 10},
        "bad": "Sometimes returns list, sometimes object"
    }
}

Tool Categories

# Different categories of tools for agents

tool_categories = {
    "information_retrieval": {
        "examples": ["search_web", "query_database", "read_file"],
        "pattern": "Input query -> Return relevant data",
        "error_handling": "Return empty results, not errors"
    },
    "data_transformation": {
        "examples": ["format_date", "convert_currency", "calculate_statistics"],
        "pattern": "Input data -> Return transformed data",
        "error_handling": "Validate inputs, return validation errors"
    },
    "external_actions": {
        "examples": ["send_email", "create_ticket", "make_purchase"],
        "pattern": "Input parameters -> Perform action -> Confirm result",
        "error_handling": "Return success/failure with details"
    },
    "system_queries": {
        "examples": ["get_current_time", "check_permissions", "get_config"],
        "pattern": "No/minimal input -> Return system state",
        "error_handling": "Should rarely fail"
    }
}

Well-Designed Tool Examples

# Example 1: Search tool with good design
search_tool = {
    "name": "search_products",
    "description": """Search the product catalog. Use this when:
- User asks about products, items, or merchandise
- User wants to find something to buy
- User asks about availability or pricing

Do NOT use this for:
- Order status (use get_order_status instead)
- Account information (use get_account_info instead)""",
    "parameters": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "Search terms (product name, description keywords)"
            },
            "category": {
                "type": "string",
                "description": "Filter by category",
                "enum": ["electronics", "clothing", "home", "sports", "all"]
            },
            "price_min": {
                "type": "number",
                "description": "Minimum price filter",
                "minimum": 0
            },
            "price_max": {
                "type": "number",
                "description": "Maximum price filter"
            },
            "in_stock_only": {
                "type": "boolean",
                "description": "Only return items currently in stock",
                "default": True
            },
            "sort_by": {
                "type": "string",
                "enum": ["relevance", "price_low", "price_high", "rating", "newest"],
                "default": "relevance"
            },
            "limit": {
                "type": "integer",
                "description": "Maximum results to return",
                "default": 10,
                "minimum": 1,
                "maximum": 50
            }
        },
        "required": ["query"]
    }
}

def search_products_handler(
    query: str,
    category: str = "all",
    price_min: float = None,
    price_max: float = None,
    in_stock_only: bool = True,
    sort_by: str = "relevance",
    limit: int = 10
) -> dict:
    """Handler for search_products tool"""

    # Build search query
    search_params = {
        "q": query,
        "limit": limit,
        "sort": sort_by
    }

    if category != "all":
        search_params["category"] = category
    if price_min is not None:
        search_params["price_gte"] = price_min
    if price_max is not None:
        search_params["price_lte"] = price_max
    if in_stock_only:
        search_params["in_stock"] = True

    # Execute search
    try:
        results = product_service.search(**search_params)

        return {
            "success": True,
            "total_results": results.total,
            "returned": len(results.items),
            "products": [
                {
                    "id": p.id,
                    "name": p.name,
                    "price": f"${p.price:.2f}",
                    "category": p.category,
                    "rating": f"{p.rating}/5",
                    "in_stock": p.in_stock,
                    "description": p.description[:100] + "..."
                }
                for p in results.items
            ],
            "filters_applied": {
                "category": category,
                "price_range": f"${price_min or 0} - ${price_max or 'any'}",
                "in_stock_only": in_stock_only
            }
        }

    except SearchException as e:
        return {
            "success": False,
            "error": "Search failed",
            "message": str(e),
            "suggestion": "Try a different search query"
        }

Error Handling Patterns

class ToolResult:
    """Standard result format for tools"""

    @staticmethod
    def success(data: any, message: str = None) -> dict:
        return {
            "success": True,
            "data": data,
            "message": message
        }

    @staticmethod
    def error(error_type: str, message: str, suggestion: str = None) -> dict:
        return {
            "success": False,
            "error_type": error_type,
            "message": message,
            "suggestion": suggestion
        }

    @staticmethod
    def not_found(item_type: str, identifier: str) -> dict:
        return ToolResult.error(
            "not_found",
            f"{item_type} '{identifier}' not found",
            f"Check the {item_type} identifier and try again"
        )

    @staticmethod
    def validation_error(field: str, issue: str) -> dict:
        return ToolResult.error(
            "validation",
            f"Invalid {field}: {issue}",
            f"Please provide a valid {field}"
        )

    @staticmethod
    def permission_denied(action: str, resource: str) -> dict:
        return ToolResult.error(
            "permission_denied",
            f"Not authorized to {action} {resource}",
            "Contact administrator if you need access"
        )

# Usage in tool handler
def get_order_status(order_id: str) -> dict:
    # Validate input
    if not order_id or not order_id.startswith("ORD-"):
        return ToolResult.validation_error("order_id", "must start with 'ORD-'")

    # Check permissions
    if not current_user.can_view_order(order_id):
        return ToolResult.permission_denied("view", f"order {order_id}")

    # Retrieve order
    order = order_service.get(order_id)
    if not order:
        return ToolResult.not_found("Order", order_id)

    # Return success
    return ToolResult.success({
        "order_id": order.id,
        "status": order.status,
        "items": len(order.items),
        "total": f"${order.total:.2f}",
        "estimated_delivery": order.estimated_delivery.isoformat()
    })

Tool Composition

# Composing multiple tools for complex operations

class CompositeToolHandler:
    """Handle tools that need to call other tools"""

    def __init__(self, tools: dict):
        self.tools = tools

    def create_order_with_notification(
        self,
        customer_id: str,
        items: list,
        notify: bool = True
    ) -> dict:
        """Composite tool that creates order and optionally notifies"""

        # Step 1: Validate customer
        customer_result = self.tools["get_customer"](customer_id)
        if not customer_result["success"]:
            return customer_result

        # Step 2: Check inventory
        for item in items:
            inventory = self.tools["check_inventory"](item["product_id"])
            if not inventory["success"] or inventory["data"]["available"] < item["quantity"]:
                return ToolResult.error(
                    "insufficient_inventory",
                    f"Not enough stock for {item['product_id']}",
                    "Reduce quantity or choose different product"
                )

        # Step 3: Create order
        order_result = self.tools["create_order"](customer_id, items)
        if not order_result["success"]:
            return order_result

        # Step 4: Send notification if requested
        if notify:
            self.tools["send_notification"](
                customer_id,
                f"Order {order_result['data']['order_id']} created"
            )

        return order_result

Tool Documentation

# Generate tool documentation for LLM

def generate_tool_documentation(tools: list) -> str:
    """Generate human and LLM readable tool documentation"""

    doc = "# Available Tools\n\n"

    for tool in tools:
        doc += f"## {tool['name']}\n\n"
        doc += f"{tool['description']}\n\n"
        doc += "### Parameters\n\n"

        params = tool.get("parameters", {}).get("properties", {})
        required = tool.get("parameters", {}).get("required", [])

        for name, spec in params.items():
            req = "(required)" if name in required else "(optional)"
            doc += f"- **{name}** {req}: {spec.get('description', 'No description')}\n"
            doc += f"  - Type: {spec.get('type', 'any')}\n"
            if "enum" in spec:
                doc += f"  - Allowed values: {', '.join(spec['enum'])}\n"
            if "default" in spec:
                doc += f"  - Default: {spec['default']}\n"

        doc += "\n### Example Usage\n\n"
        # Add example if available
        doc += "\n---\n\n"

    return doc

Well-designed tools enable reliable AI agent behavior. Tomorrow, I will cover multi-turn conversation patterns.

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.