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

Tools Guide

Learn more about creating tools

Hooks System

Deep dive into the hooks system

Agent Configuration

Configure agents to use your plugins

Examples

See more plugin examples