Skip to main content
Use expressive Python type annotations in tool signatures and agents understand exactly what parameters they can pass.
from typing import Literal
from praisonaiagents import Agent, tool

@tool
def analyse(text: str, mode: Literal["fast", "deep"] = "fast") -> str:
    """Analyse text with a fast or deep mode."""
    return f"{mode} analysis of: {text}"

agent = Agent(name="Analyst", tools=[analyse])
agent.start("Analyse this document in deep mode")
The user asks the agent to call a typed tool; annotations become JSON Schema so the model passes valid parameters.

Quick Start

1

Optional Parameter

Create a tool where parameters can be omitted by the agent:
from typing import Optional
from praisonaiagents import Agent, tool

@tool
def search(query: str, max_results: Optional[int] = None) -> str:
    """Search the web. Leave max_results blank to use the default."""
    limit = max_results or 10
    return f"Searched '{query}' (limit={limit})"

agent = Agent(
    instructions="You search the web for information",
    tools=[search]
)

agent.start("Find AI news")
2

Literal for Fixed Choices

Restrict parameter values to specific literal options:
from typing import Literal
from praisonaiagents import Agent, tool

@tool
def analyze(text: str, mode: Literal["fast", "deep"] = "fast") -> str:
    """Analyze text with different modes."""
    if mode == "deep":
        return f"Deep analysis of: {text}"
    return f"Quick analysis of: {text}"

agent = Agent(
    instructions="You analyze text content",
    tools=[analyze]
)

agent.start("Analyze this document in deep mode")
3

Enum for Choices

Use Enum classes for reusable choice sets across tools:
from enum import Enum
from praisonaiagents import Agent, tool

class Priority(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

@tool
def create_task(title: str, priority: Priority = Priority.MEDIUM) -> str:
    """Create a new task with specified priority."""
    return f"Created task '{title}' with priority: {priority.value}"

agent = Agent(
    instructions="You manage tasks",
    tools=[create_task]
)

agent.start("Create a high priority task for the meeting")

How It Works

The agent converts your Python type annotations into JSON Schema before the LLM sees the tool definition.
Python AnnotationJSON Schema Output
str, int, float, bool{"type": "string"} and related primitives
Optional[int]{"anyOf": [{"type": "integer"}, {"type": "null"}]}
Union[str, int]{"anyOf": [{"type": "string"}, {"type": "integer"}]}
Literal["fast", "deep"]{"type": "string", "enum": ["fast", "deep"]}
Enum subclass (str-valued){"type": "string", "enum": ["low", "medium", "high"]}
List[int]{"type": "array", "items": {"type": "integer"}}
Dict[str, int]{"type": "object", "additionalProperties": {"type": "integer"}}

Common Patterns

Mode Flags with Defaults

@tool
def process_data(data: str, mode: Literal["fast", "thorough"] = "fast") -> str:
    """Process data with different thoroughness levels."""
    if mode == "thorough":
        return f"Thoroughly processed: {data}"
    return f"Quick processing: {data}"

Shared Choice Sets

class Format(str, Enum):
    JSON = "json"
    CSV = "csv"
    XML = "xml"

@tool
def export_data(data: str, format: Format) -> str:
    """Export data in specified format."""
    return f"Exported as {format.value}: {data}"

@tool
def import_data(file_path: str, format: Format) -> str:
    """Import data from specified format."""
    return f"Imported {format.value} file: {file_path}"

Structured Collections

from typing import List, Dict

@tool
def batch_process(items: List[str], config: Dict[str, str]) -> str:
    """Process multiple items with configuration."""
    return f"Processed {len(items)} items with config: {config}"

Best Practices

Use Optional[int] with a default of None instead of leaving parameters untyped. This tells the agent the parameter can be omitted and what type to use when provided.
# Good
def search(query: str, limit: Optional[int] = None) -> str:
    pass

# Avoid
def search(query: str, limit=None) -> str:
    pass
Choose Literal[...] when values are unique to one tool. Use Enum when the same choices appear across multiple tools.
# Literal for one-off choices
def analyze(text: str, depth: Literal["surface", "deep"] = "surface"):
    pass

# Enum for shared choices
class Priority(str, Enum):
    LOW = "low"
    HIGH = "high"

def create_task(title: str, priority: Priority):
    pass

def update_task(id: str, priority: Priority):
    pass
Don’t use Union[str, dict] for “either a string or a dict” - the LLM struggles with such choices. Create separate tools instead.
# Avoid
def process(input: Union[str, Dict[str, str]]):
    pass

# Prefer
def process_text(text: str):
    pass

def process_data(data: Dict[str, str]):
    pass
Write List[str] instead of bare list so the agent knows what elements to include.
# Good - agent knows to pass strings
def process_items(items: List[str]) -> str:
    pass

# Avoid - agent doesn't know element type
def process_items(items: list) -> str:
    pass

Tool Schema Validation

Validate tool schemas and catch type errors

Custom Tools Guide

Complete guide to creating custom tools

Dynamic Tool Schemas

Generate schemas dynamically at runtime

Different Ways to Create Tools

Explore all methods for tool creation