Skip to main content

Single-File Plugins

PraisonAI supports a simplified plugin format inspired by WordPress - just a single Python file with metadata in a docstring header.
Single-file plugins are the easiest way to extend PraisonAI with custom tools and hooks. No package structure, no configuration files - just one .py file.

Quick Start

1. Create a Plugin

Create a file in ~/.praisonai/plugins/ or ./.praison/plugins/:
"""
Plugin Name: Weather Tools
Description: Get weather information for any city
Version: 1.0.0
Author: Your Name
"""

from praisonaiagents import tool

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city.
    
    Args:
        city: Name of the city
        
    Returns:
        Weather information string
    """
    # Your implementation here
    return f"The weather in {city} is sunny, 72°F"

@tool
def get_forecast(city: str, days: int = 5) -> str:
    """Get weather forecast for a city.
    
    Args:
        city: Name of the city
        days: Number of days to forecast
        
    Returns:
        Forecast information
    """
    return f"{days}-day forecast for {city}: Sunny with occasional clouds"

2. Use in Your Agent

Tools from plugins are automatically discovered:
from praisonaiagents import Agent

# Tools are auto-discovered from plugin directories
agent = Agent(
    name="Weather Assistant",
    instructions="Help users with weather information",
    tools=["get_weather", "get_forecast"]  # Reference by name
)

response = agent.start("What's the weather in Paris?")

Plugin Header Format

The plugin header is a docstring at the top of the file with WordPress-style metadata:
"""
Plugin Name: My Plugin          # Required - Display name
Description: What it does       # Optional - Brief description
Version: 1.0.0                  # Optional - Semantic version
Author: Your Name               # Optional - Author name
Hooks: before_tool, after_tool  # Optional - Hooks this plugin uses
Dependencies: requests, pandas  # Optional - Required packages
"""

Required Fields

FieldDescription
Plugin NameThe display name of your plugin

Optional Fields

FieldDescriptionDefault
DescriptionBrief description of functionalityEmpty
VersionSemantic version (e.g., 1.0.0)1.0.0
AuthorPlugin author nameNone
HooksComma-separated list of hooks usedNone
DependenciesComma-separated list of required packagesNone

Plugin Directories

Plugins are discovered from these directories (in order):
  1. Project-level: ./.praison/plugins/ - Plugins specific to your project
  2. User-level: ~/.praisonai/plugins/ - Plugins available to all your projects
Use project-level plugins for project-specific tools and user-level plugins for tools you want everywhere.

Adding Tools

Use the @tool decorator to create tools:
"""
Plugin Name: Math Tools
Description: Mathematical operations
Version: 1.0.0
"""

from praisonaiagents import tool

@tool
def calculate(expression: str) -> float:
    """Safely evaluate a mathematical expression.
    
    Args:
        expression: Math expression like "2 + 2" or "sqrt(16)"
        
    Returns:
        The calculated result
    """
    import ast
    import operator
    
    # Safe evaluation implementation
    return eval(expression)  # Use safe eval in production!

@tool
def convert_units(value: float, from_unit: str, to_unit: str) -> float:
    """Convert between units.
    
    Args:
        value: The value to convert
        from_unit: Source unit (e.g., "km")
        to_unit: Target unit (e.g., "miles")
        
    Returns:
        Converted value
    """
    conversions = {
        ("km", "miles"): 0.621371,
        ("miles", "km"): 1.60934,
    }
    factor = conversions.get((from_unit, to_unit), 1.0)
    return value * factor

Adding Hooks

Use the add_hook decorator to intercept agent lifecycle events:
"""
Plugin Name: Logging Plugin
Description: Log all tool calls
Version: 1.0.0
Hooks: before_tool, after_tool
"""

from praisonaiagents import tool
from praisonaiagents.hooks import add_hook

@add_hook("before_tool")
def log_tool_call(tool_name: str, args: dict) -> dict:
    """Log before each tool call."""
    print(f"[LOG] Calling tool: {tool_name} with args: {args}")
    return args  # Return args (can modify them)

@add_hook("after_tool")
def log_tool_result(tool_name: str, result: any) -> any:
    """Log after each tool call."""
    print(f"[LOG] Tool {tool_name} returned: {result}")
    return result  # Return result (can modify it)

@tool
def my_tool(query: str) -> str:
    """A sample tool."""
    return f"Result for: {query}"

Available Hooks

HookDescriptionParameters
before_toolBefore a tool is calledtool_name, args
after_toolAfter a tool returnstool_name, result
before_agentBefore agent processesagent, context
after_agentAfter agent completesagent, result
before_llmBefore LLM callmessages, config
after_llmAfter LLM responseresponse, usage

CLI Commands

# Create a new plugin with template
praisonai plugins init my_plugin

# With options
praisonai plugins init weather_tools --author "John Doe" --with-hook

# In a specific directory
praisonai plugins init custom --output ./my_plugins/

Programmatic Usage

Load Plugins Manually

from praisonaiagents import (
    load_plugin,
    discover_plugins,
    discover_and_load_plugins,
)

# Load a single plugin
result = load_plugin("./my_plugin.py")
print(f"Loaded: {result['name']}, tools: {result['tools']}")

# Discover plugins without loading
plugins = discover_plugins(["./plugins", "~/.praisonai/plugins"])
for p in plugins:
    print(f"Found: {p['name']} at {p['path']}")

# Discover and load all
loaded = discover_and_load_plugins(include_defaults=True)
print(f"Loaded {len(loaded)} plugins")

Use with PluginManager

from praisonaiagents import get_plugin_manager

manager = get_plugin_manager()

# Load single-file plugins
manager.load_single_file_plugin("./my_plugin.py")

# Auto-discover from default directories
count = manager.auto_discover_plugins()
print(f"Discovered {count} plugins")

# List loaded plugins
for plugin in manager.list_single_file_plugins():
    print(f"- {plugin['name']}: {plugin['tools']}")

Best Practices

Keep It Simple

One plugin = one purpose. Don’t cram unrelated tools into one file.

Document Tools

Use clear docstrings - they become the tool descriptions for the LLM.

Handle Errors

Return meaningful error messages instead of raising exceptions.

Type Hints

Always use type hints - they help generate accurate tool schemas.

Good Plugin Example

"""
Plugin Name: GitHub Tools
Description: Interact with GitHub repositories
Version: 1.0.0
Author: Your Name
Dependencies: requests
"""

from praisonaiagents import tool
from typing import List, Dict, Any

@tool
def get_repo_info(owner: str, repo: str) -> Dict[str, Any]:
    """Get information about a GitHub repository.
    
    Args:
        owner: Repository owner (username or org)
        repo: Repository name
        
    Returns:
        Dictionary with repo info (stars, forks, description, etc.)
    """
    import requests
    
    try:
        response = requests.get(
            f"https://api.github.com/repos/{owner}/{repo}",
            timeout=10
        )
        response.raise_for_status()
        data = response.json()
        
        return {
            "name": data["name"],
            "description": data["description"],
            "stars": data["stargazers_count"],
            "forks": data["forks_count"],
            "language": data["language"],
            "url": data["html_url"],
        }
    except requests.RequestException as e:
        return {"error": f"Failed to fetch repo: {str(e)}"}

@tool
def list_repo_issues(owner: str, repo: str, state: str = "open") -> List[Dict[str, Any]]:
    """List issues for a GitHub repository.
    
    Args:
        owner: Repository owner
        repo: Repository name
        state: Issue state - "open", "closed", or "all"
        
    Returns:
        List of issues with title, number, and state
    """
    import requests
    
    try:
        response = requests.get(
            f"https://api.github.com/repos/{owner}/{repo}/issues",
            params={"state": state, "per_page": 10},
            timeout=10
        )
        response.raise_for_status()
        
        return [
            {
                "number": issue["number"],
                "title": issue["title"],
                "state": issue["state"],
                "url": issue["html_url"],
            }
            for issue in response.json()
        ]
    except requests.RequestException as e:
        return [{"error": f"Failed to fetch issues: {str(e)}"}]

Troubleshooting

  • Check the file is in .praison/plugins/ or ~/.praisonai/plugins/
  • Ensure the file has a .py extension
  • Verify the docstring header has Plugin Name: field
  • Files starting with _ are skipped
  • Make sure you’re using the @tool decorator
  • Check for import errors in your plugin
  • Run with verbose logging to see errors
  • Verify the hook name is correct (e.g., before_tool)
  • Check that the hook function signature matches expected parameters
  • Ensure the plugin is loaded before the agent runs

Next Steps