Skip to Content
DocsToolsPublishing Tools

Publishing Tools from Your App

If you’re building a Frappe app, you can publish tools that agents can automatically discover and use. This lets you share reusable agent capabilities across projects and with the community.

What Are Published Tools?

Published tools are Python functions in your Frappe app that agents can automatically discover and use. Instead of manually creating Agent Tool Functions, you decorate your functions with @agent_tool, and Huf automatically syncs them when your app is installed.

Published tools provide several advantages over manually configured tool functions.

Auto-discoveryTools appear automatically when app is installed
No manual setupNo need to create Agent Tool Function records
Version controlTools are versioned with your app
ReusableShare tools across multiple sites/projects
MaintainableUpdate tools by updating your app

How It Works

Published tools are discovered and synced automatically through a simple lifecycle.

The Flow

Publishing a tool follows a predictable path from code to agent availability.

Auto-Sync Process

When your app is installed or updated, Huf performs several sync actions.

ScanHuf scans your app for @agent_tool decorated functions
Create or UpdateCreates or updates Agent Tool Function records
LinkLinks tools to your app for tracking
AvailabilityMakes tools available to all agents
Sync happens automatically during:
  • App installation (bench install-app)
  • App migration (bench migrate)
  • Manual sync (via Huf hooks)

Creating a Published Tool

Follow these steps to publish a tool from your Frappe app.

1

Write Your Function

Create a function in your app with proper structure:

File: my_app/api/tools.py

import frappe from huf.ai.tool_discovery import agent_tool @agent_tool def calculate_discount(customer_type: str, order_total: float) -> dict: """ Calculate discount percentage and amount based on customer type and order total. This tool helps agents determine appropriate discounts for customers. Args: customer_type (str): Type of customer (Wholesale, Retail, Distributor) order_total (float): Total order amount before discount Returns: dict: Discount details including percentage, amount, and final total Example: >>> calculate_discount("Wholesale", 1000.0) {"discount_pct": 15.0, "discount_amount": 150.0, "final_total": 850.0} """ # Define discount rules discount_rules = { "Wholesale": 0.15, # 15% discount "Distributor": 0.20, # 20% discount "Retail": 0.05 # 5% discount } # Get base discount from customer type base_discount = discount_rules.get(customer_type, 0.0) # Additional discount for large orders if order_total > 10000: base_discount += 0.05 # Extra 5% for orders over $10k # Calculate discount amount discount_amount = order_total * base_discount final_total = order_total - discount_amount return { "customer_type": customer_type, "order_total": order_total, "discount_pct": round(base_discount * 100, 2), "discount_amount": round(discount_amount, 2), "final_total": round(final_total, 2) }
2

Import the Decorator

Make sure to import agent_tool from Huf:

from huf.ai.tool_discovery import agent_tool

If Huf isn’t installed, handle gracefully:

try: from huf.ai.tool_discovery import agent_tool except ImportError: # Huf not installed, define a no-op decorator def agent_tool(func): return func
3

Add Metadata (Optional)

The decorator accepts optional metadata to improve discovery.

@agent_tool( category="Pricing", description="Calculate discounts for customers", tags=["pricing", "discount", "customer"] ) def calculate_discount(customer_type: str, order_total: float) -> dict: # Function implementation pass
OptionPurpose
categoryGroup tools by category (e.g., “Pricing”, “Inventory”, “Sales”)
descriptionOverride auto-generated description
tagsSearchable tags for tool discovery
4

Install Your App

Install your app on a Frappe site that has Huf enabled.

bench --site sitename install-app my_app

Huf automatically:

ScanScans your app for @agent_tool functions
CreateCreates Agent Tool Function records
LinkLinks them to your app

Check in Desk: Navigate to Huf → Agent Tool Function and filter by your app name.

Tool Metadata from Docstrings

Huf extracts metadata from your function docstring to build tool records.

Function Description

The main docstring becomes the tool description.

@agent_tool def get_customer_orders(customer_id: str): """ Retrieve all sales orders for a customer with order details, status, and totals. Use this when users ask about customer order history or purchase patterns. """ pass

Follow these guidelines when writing descriptions:

Best Practices:

  • Start with what the tool does
  • Mention when agents should use it
  • Include use case examples
  • Be specific and clear

Parameter Documentation

Document parameters in the docstring so agents know what to pass.

@agent_tool def create_quote(items: list, customer_id: str, valid_until: str = None): """ Create a sales quote with items for a customer. Args: items (list): List of items with 'item_code', 'qty', 'rate' customer_id (str): Customer ID to quote for valid_until (str, optional): Quote expiry date (YYYY-MM-DD) Returns: dict: Created quote details with quote ID """ pass

Keep these guidelines in mind when documenting parameters:

Parameter Types:

  • Document types clearly (str, int, float, list, dict)
  • Mark optional parameters
  • Provide example values
  • Explain complex structures

Return Value Documentation

Document what the function returns to help agents interpret results.

@agent_tool def analyze_sales_trends(period_days: int = 30): """ Analyze sales trends over a period. Returns: dict: Analysis results with: - total_revenue (float): Total sales in period - order_count (int): Number of orders - top_customers (list): Top 5 customers by revenue - growth_rate (float): Percentage growth vs previous period """ pass

Advanced Patterns

These patterns help you organize and conditionally expose tools.

Tool Categories

Group related tools with categories so agents can discover them by context.

# Pricing tools @agent_tool(category="Pricing") def calculate_discount(...): pass @agent_tool(category="Pricing") def apply_promotion_code(...): pass # Inventory tools @agent_tool(category="Inventory") def check_stock_levels(...): pass @agent_tool(category="Inventory") def calculate_reorder_quantity(...): pass

Agents can discover tools by category, making it easier to find relevant capabilities.

Conditional Tool Registration

Register tools only when dependencies are present.

import frappe @agent_tool def advanced_analytics(...): """Advanced analytics (requires Analytics app).""" if not frappe.db.exists("Module Def", {"name": "Analytics"}): frappe.throw("Analytics module not installed") # Implementation pass

Tool Dependencies

Document dependencies in docstrings so agents know prerequisites.

@agent_tool def sync_with_external_system(...): """ Sync data with external system. Requires: - External API credentials configured - Network access to external system - Valid API key in app settings """ pass

Tool Discovery Process

Huf finds and registers your tools through a structured scan.

How Huf Finds Your Tools

The discovery process runs through these stages.

Module Scanning

Huf scans specific locations in your app.

Huf scans these locations:

  • {app_name}/api/ directory
  • {app_name}/utils/ directory
  • Any module imported by your app’s hooks

Best Practice: Put tools in {app_name}/api/tools.py for clarity.

Error Handling

Discovery continues even when individual tools fail.

If a tool function has errors:

  • Huf logs the error but continues scanning
  • Other tools are still registered
  • Check logs for specific tool errors
  • Fix errors and re-run migration

Updating Published Tools

Tools stay in sync with your app as you release updates.

Modifying Tools

When you update a tool function, migration propagates the changes.

The following changes are synchronized:

Changes that sync:

  • Function signature (parameters)
  • Docstring (description)
  • Decorator metadata
  • Function implementation (not stored, but used)

Versioning

Tools are versioned with your app.

  • App version tracks tool versions
  • Update app to update tools
  • Rollback app to rollback tools

Breaking Changes

If you make breaking changes, choose one of these strategies.

Option 1: Deprecate and Replace

@agent_tool def old_tool_name(...): """DEPRECATED: Use new_tool_name instead.""" frappe.msgprint("This tool is deprecated. Use new_tool_name.") return new_tool_name(...) @agent_tool def new_tool_name(...): """Improved version of old tool.""" pass

Option 2: Maintain Compatibility

@agent_tool def tool_name(param1, param2=None, **kwargs): """Handle both old and new parameter formats.""" # Support old format if param2 is None and 'old_param' in kwargs: param2 = kwargs['old_param'] # New implementation pass

Best Practices

These guidelines help you design reliable, maintainable tools.

Function Design

Keep tool functions focused and well documented.

Do:

  • Write focused, single-purpose functions
  • Use clear, descriptive names
  • Document everything in docstrings
  • Handle errors gracefully
  • Return structured data (dicts)
  • Add type hints for parameters

Don’t:

  • Create mega-functions that do everything
  • Use vague parameter names
  • Forget error handling
  • Return raw database objects
  • Make functions depend on global state

Documentation

Comprehensive docstrings make tools easier for agents to use.

@agent_tool def process_order(order_id: str, payment_method: str) -> dict: """ Process a sales order with payment and inventory updates. This tool handles the complete order processing workflow including payment validation, inventory deduction, and order confirmation. Args: order_id (str): The sales order ID to process payment_method (str): Payment method (Card, Bank Transfer, Cash) Returns: dict: Processing result with: - success (bool): Whether processing succeeded - invoice_id (str): Generated invoice ID if successful - errors (list): Any errors encountered Raises: frappe.ValidationError: If order is invalid or cannot be processed Example: >>> process_order("SO-2024-001", "Card") {"success": True, "invoice_id": "INV-2024-001", "errors": []} Note: This tool requires: - Order must be in Draft status - Sufficient inventory available - Valid payment method configured """ pass

Error Handling

Always handle errors so agents receive useful feedback.

@agent_tool def safe_operation(param: str) -> dict: """Safe operation with error handling.""" try: # Validate input if not param: frappe.throw("Parameter is required") # Perform operation result = perform_operation(param) return {"success": True, "result": result} except frappe.ValidationError as e: # User errors - return to agent return {"success": False, "error": str(e)} except Exception as e: # System errors - log and return generic message frappe.log_error(f"Tool error: {str(e)}") return {"success": False, "error": "An error occurred processing your request"}

Testing Tools

Test your tools before publishing to catch regressions early.

# In your app's test file import unittest class TestTools(unittest.TestCase): def test_calculate_discount(self): from my_app.api.tools import calculate_discount result = calculate_discount("Wholesale", 1000.0) self.assertEqual(result["discount_pct"], 15.0) self.assertEqual(result["discount_amount"], 150.0) self.assertEqual(result["final_total"], 850.0)

Sharing Tools

Published tools can be shared inside your organization or with the wider community.

Within Your Organization

Install your app on multiple sites to share tools internally.

Multiple Sites:

  • Install your app on all sites
  • Tools automatically available everywhere
  • Consistent toolset across sites

Version Control:

  • Tools in git with your app
  • Version controlled
  • Easy to rollback

With the Community

Open-source your app to let others use and contribute to your tools.

Open Source Apps:

  • Publish your app on GitHub
  • Others can install and use your tools
  • Contribute to the Huf ecosystem

Documentation:

  • Document your tools in app README
  • Explain use cases
  • Provide examples

Troubleshooting

If tools don’t appear or update as expected, check these common causes.

Tools not appearing:

  • Check app is installed: bench --site sitename list-apps
  • Verify decorator import: from huf.ai.tool_discovery import agent_tool
  • Run migration: bench --site sitename migrate
  • Check logs for discovery errors

Tools not updating:

  • Run migration after code changes
  • Clear cache: bench --site sitename clear-cache
  • Check Agent Tool Function records in Desk

Import errors:

  • Ensure Huf is installed: bench --site sitename list-apps | grep huf
  • Check Python path and imports
  • Verify function is in scanned directory

Permission errors:

  • Tools respect Frappe permissions
  • Check user has access to underlying DocTypes
  • Verify function doesn’t bypass permissions

Example: Complete Tool Package

Here is a complete example of a tool package in one module.

File: my_app/api/tools.py

""" Agent tools for My App. This module provides reusable tools for Huf agents. """ import frappe from huf.ai.tool_discovery import agent_tool from typing import List, Dict, Optional @agent_tool(category="Sales") def get_customer_orders(customer_id: str, status: Optional[str] = None) -> Dict: """ Retrieve sales orders for a customer. Args: customer_id (str): Customer ID status (str, optional): Filter by status (Draft, Submitted, etc.) Returns: dict: List of orders with details """ filters = {"customer": customer_id} if status: filters["status"] = status orders = frappe.get_all( "Sales Order", filters=filters, fields=["name", "grand_total", "status", "delivery_date"] ) return { "customer_id": customer_id, "order_count": len(orders), "orders": orders } @agent_tool(category="Inventory") def check_item_availability(item_code: str, warehouse: Optional[str] = None) -> Dict: """ Check item stock availability. Args: item_code (str): Item code to check warehouse (str, optional): Specific warehouse, or all if None Returns: dict: Stock levels and availability """ filters = {"item_code": item_code} if warehouse: filters["warehouse"] = warehouse bins = frappe.get_all( "Bin", filters=filters, fields=["warehouse", "actual_qty", "reserved_qty"] ) total_qty = sum(bin.actual_qty for bin in bins) total_reserved = sum(bin.reserved_qty for bin in bins) available = total_qty - total_reserved return { "item_code": item_code, "total_quantity": total_qty, "reserved_quantity": total_reserved, "available_quantity": available, "warehouses": [ { "warehouse": bin.warehouse, "quantity": bin.actual_qty, "available": bin.actual_qty - bin.reserved_qty } for bin in bins ] } @agent_tool(category="Pricing") def calculate_order_total(items: List[Dict], customer_id: str) -> Dict: """ Calculate total order amount with discounts and taxes. Args: items (list): List of items with 'item_code', 'qty', 'rate' customer_id (str): Customer ID for pricing rules Returns: dict: Calculated totals with breakdown """ # Implementation subtotal = sum(item["qty"] * item["rate"] for item in items) # Get customer discount customer = frappe.get_doc("Customer", customer_id) discount_pct = customer.default_discount_percentage or 0 discount_amount = subtotal * (discount_pct / 100) # Calculate tax (simplified) tax_rate = 0.10 # 10% tax tax_amount = (subtotal - discount_amount) * tax_rate total = subtotal - discount_amount + tax_amount return { "subtotal": round(subtotal, 2), "discount_percentage": discount_pct, "discount_amount": round(discount_amount, 2), "tax_rate": tax_rate, "tax_amount": round(tax_amount, 2), "grand_total": round(total, 2) }

What’s Next?

Continue with these related guides.


Questions? Visit GitHub discussions  or check the Huf source code .

Last updated on