Recipe Author Persona

Create and maintain PraisonAI recipes

Design, test, and document recipes with clear inputs, outputs, and agent instructions.

python
from praisonaiagents import Agent

agent = Agent(
    name="Recipe Author",
    instructions="Draft a support-reply recipe with eval-friendly outputs.",
)
agent.start("Define inputs and success criteria for a refund FAQ recipe.")

The user publishes recipes that app teams consume through SDK, CLI, or HTTP integrations.

Diagram

Input

Agent

Output

Primary Goals

  • Create effective recipes that solve specific problems
  • Design clear interfaces with well-defined inputs/outputs
  • Write comprehensive tests for reliability
  • Document thoroughly for easy adoption

Recommended Knowledge

Typical Workflow

  1. Define the Problem
    yaml
    # Start with a clear problem statement
    # What task does this recipe automate?
    # Who is the target user?
    # What are the expected inputs and outputs?
    
    # Example: Support Reply Drafter
    # Problem: Support agents spend too much time drafting replies
    # User: Support team members
    # Input: Ticket ID, customer message
    # Output: Draft reply text
  2. Create Recipe Structure
    bash
    # Initialize recipe directory
    mkdir -p ~/.praisonai/templates/support-reply-drafter
    cd ~/.praisonai/templates/support-reply-drafter
    yaml
    # TEMPLATE.yaml
    name: support-reply-drafter
    version: "1.0.0"
    description: "Generate professional support reply drafts"
    author: "Your Name"
    license: "MIT"
    
    tags:
      - support
      - customer-service
      - text-generation
    
    requires:
      env:
        - OPENAI_API_KEY
      packages:
        - praisonaiagents
    
    inputs:
      ticket_id:
        type: string
        description: "Support ticket identifier"
        required: true
      message:
        type: string
        description: "Customer message to respond to"
        required: true
      tone:
        type: string
        description: "Response tone"
        default: "professional"
        enum: ["professional", "friendly", "formal"]
    
    outputs:
      reply:
        type: string
        description: "Generated reply draft"
      confidence:
        type: number
        description: "Confidence score (0-1)"
    
    cli:
      command: "praisonai recipe run support-reply-drafter"
      examples:
        - 'praisonai recipe run support-reply-drafter --input ''{"ticket_id": "T-123", "message": "I need help"}'''
    
    safety:
      dry_run_default: false
      requires_consent: false
  3. Implement the Recipe
    python
    # recipe.py
    from praisonaiagents import Agent, Task, AgentTeam
    
    def run(input_data: dict, config: dict = None) -> dict:
        """
        Generate a support reply draft.
    
        Args:
            input_data: Contains ticket_id, message, and optional tone
            config: Optional configuration overrides
    
        Returns:
            Dict with reply and confidence score
        """
        ticket_id = input_data.get("ticket_id")
        message = input_data.get("message")
        tone = input_data.get("tone", "professional")
    
        # Create support agent
        support_agent = Agent(
            name="Support Specialist",
            role="Customer Support Expert",
            goal=f"Draft a {tone} reply to customer inquiries",
            instructions="""
            You are an expert customer support specialist.
            - Be helpful and empathetic
            - Address the customer's concern directly
            - Provide clear next steps if applicable
            - Keep responses concise but complete
            """,
        )
    
        # Create task
        task = Task(
            name="draft_reply",
            description=f"""
            Draft a {tone} reply to this customer message:
    
            Ticket: {ticket_id}
            Message: {message}
    
            Provide a professional, helpful response.
            """,
            expected_output="A well-crafted support reply",
            agent=support_agent,
        )
    
        # Execute
        agents = AgentTeam(
            agents=[support_agent],
            tasks=[task],
        )
    
        result = agents.start()
    
        return {
            "reply": result.get("task_output", ""),
            "confidence": 0.85,  # Could be calculated from model response
        }
  4. Write Tests
    python
    # test_recipe.py
    import pytest
    from recipe import run
    
    def test_basic_reply():
        """Test basic reply generation."""
        result = run({
            "ticket_id": "T-123",
            "message": "I can't log into my account",
        })
    
        assert "reply" in result
        assert len(result["reply"]) > 50
        assert result["confidence"] > 0.5
    
    def test_tone_professional():
        """Test professional tone."""
        result = run({
            "ticket_id": "T-124",
            "message": "Your product is broken!",
            "tone": "professional",
        })
    
        assert "reply" in result
        # Should not contain informal language
        assert "hey" not in result["reply"].lower()
    
    def test_tone_friendly():
        """Test friendly tone."""
        result = run({
            "ticket_id": "T-125",
            "message": "How do I reset my password?",
            "tone": "friendly",
        })
    
        assert "reply" in result
    
    def test_missing_required_field():
        """Test error handling for missing fields."""
        with pytest.raises(ValueError):
            run({"ticket_id": "T-126"})  # Missing message
    
    @pytest.mark.integration
    def test_end_to_end():
        """Full integration test."""
        from praisonai import recipe
    
        result = recipe.run(
            "support-reply-drafter",
            input={
                "ticket_id": "T-127",
                "message": "I need a refund",
            }
        )
    
        assert result.ok
        assert result.output.get("reply")
  5. Document the Recipe

    Create a README.md with:

    • Title and description – What the recipe does
    • Quick start – Minimal example to get started
    • Inputs table – All parameters with types and descriptions
    • Outputs table – What the recipe returns
    • Examples – Multiple usage scenarios
    • Requirements – Dependencies and environment variables
    • Troubleshooting – Common issues and solutions
    • Example README structure:
    text
    # Recipe Name
    
    Description of what the recipe does.
    
    ## Quick Start
    praisonai recipe run my-recipe --input '{"key": "value"}'
    
    ## Inputs
    | Field | Type | Required | Description |
    |-------|------|----------|-------------|
    | field1 | string | Yes | Description |
    
    ## Outputs
    | Field | Type | Description |
    |-------|------|-------------|
    | output1 | string | Description |
    
    ## Examples
    [Code examples for different use cases]
    
    ## Requirements
    - OPENAI_API_KEY environment variable
    - praisonaiagents package
    
    ## Troubleshooting
    Common issues and solutions

Key Concerns

Interface Design

yaml
# Good: Clear, typed inputs with defaults
inputs:
  query:
    type: string
    required: true
    description: "Search query"
  max_results:
    type: integer
    default: 10
    minimum: 1
    maximum: 100
    description: "Maximum results to return"

# Bad: Vague, untyped inputs
inputs:
  data:
    description: "Input data"

Error Handling

python
def run(input_data: dict, config: dict = None) -> dict:
    """Recipe with proper error handling."""

    # Validate required inputs
    if not input_data.get("query"):
        raise ValueError("query is required")

    try:
        # Main logic
        result = process(input_data)
        return {"output": result, "ok": True}

    except RateLimitError:
        return {"error": "Rate limit exceeded, try again later", "ok": False}
    except InvalidInputError as e:
        return {"error": f"Invalid input: {e}", "ok": False}
    except Exception as e:
        # Log for debugging but don't expose internals
        logger.error(f"Recipe failed: {e}")
        return {"error": "An unexpected error occurred", "ok": False}

Testing Strategy

python
# Unit tests - fast, isolated
def test_input_validation():
    with pytest.raises(ValueError):
        run({})

# Integration tests - with real APIs
@pytest.mark.integration
def test_with_real_api():
    result = run({"query": "test"})
    assert result["ok"]

# Snapshot tests - for output consistency
def test_output_format(snapshot):
    result = run({"query": "hello"})
    assert result == snapshot

Best Practices

Recipe naming
✅ Good names:
- support-reply-drafter
- code-review-assistant
- document-summarizer

❌ Bad names:
- my-recipe
- test123
- agent
Version management
yaml
# Use semantic versioning
version: "1.2.3"
# 1 = major (breaking changes)
# 2 = minor (new features)
# 3 = patch (bug fixes)
Dependency management
yaml
requires:
  packages:
    - praisonaiagents>=0.5.0  # Specify minimum versions
  env:
    - OPENAI_API_KEY  # Required
  optional_env:
    - LANGCHAIN_API_KEY  # Optional enhancement

Troubleshooting

Recipe not discovered

Check recipe location:

bash
# Recipes are discovered from:
ls ~/.praisonai/templates/
ls ~/.config/praison/templates/
ls ./.praison/templates/
Tests failing in CI
  • Ensure API keys are set in CI secrets
  • Use mocks for unit tests
  • Mark integration tests appropriately
Users report inconsistent outputs
  • Add temperature control to prompts
  • Implement output validation
  • Add retry logic for edge cases

Handoff Checklist

Before releasing a recipe:

  • [ ] TEMPLATE.yaml is complete and valid
  • [ ] All inputs have descriptions and types
  • [ ] All outputs are documented
  • [ ] README.md with examples
  • [ ] Unit tests pass
  • [ ] Integration tests pass
  • [ ] Tested with all integration models (SDK, CLI, HTTP)
  • [ ] Error messages are user-friendly
  • [ ] Dependencies are specified with versions
  • [ ] License is specified

Next Steps


SDK & Recipe Author Reference (Copy/Paste for AI)

1. Recipe Discovery

Recipes are discovered from these directories in order of precedence (first match wins):

PriorityPathDescription
1~/.praisonai/templates/User-local recipes
2~/.config/praison/templates/XDG config location
3./.praison/templates/Project-local recipes
4/path/to/Agent-Recipes/agent_recipes/templates/Built-in recipes

Source: praisonai/cli/features/recipes.py → RECIPE_PATHS, find_recipe_paths()

2. Recipe Directory Structure

my-recipe/
├── TEMPLATE.yaml      # Required: Recipe metadata and configuration
├── README.md          # Required: Documentation
├── recipe.py          # Required: Main entrypoint with run() function
├── test_recipe.py     # Recommended: Tests
└── requirements.txt   # Optional: Additional dependencies

3. TEMPLATE.yaml Complete Schema

yaml
# ═══════════════════════════════════════════════════════════════════════════
# TEMPLATE.yaml - Complete Schema Reference
# ═══════════════════════════════════════════════════════════════════════════

# ─────────────────────────────────────────────────────────────────────────────
# METADATA (Required)
# ─────────────────────────────────────────────────────────────────────────────
name: my-recipe-name              # Required: Unique identifier (kebab-case)
version: "1.0.0"                  # Required: Semantic version string
description: "Short description"  # Required: One-line description
author: "Your Name"               # Required: Author name or handle
license: "MIT"                    # Required: SPDX license identifier

# ─────────────────────────────────────────────────────────────────────────────
# TAGS (Required)
# ─────────────────────────────────────────────────────────────────────────────
tags:                             # Required: At least one tag
  - video                         # Category tags for discovery
  - audio
  - transcription

# ─────────────────────────────────────────────────────────────────────────────
# REQUIREMENTS (Required)
# ─────────────────────────────────────────────────────────────────────────────
requires:
  # Environment variables (checked at runtime)
  env:                            # List of required env vars
    - OPENAI_API_KEY
    - ANTHROPIC_API_KEY

  # Optional environment variables (not blocking)
  optional_env:                   # List of optional env vars
    - LANGCHAIN_API_KEY

  # Python packages (checked via import)
  packages:                       # List of pip packages
    - praisonaiagents
    - openai>=1.0.0
    - pandas

  # External system tools (checked via shutil.which)
  external:                       # List of CLI tools
    - ffmpeg
    - tesseract
    - pandoc

  # Recipe tools from praisonai-tools
  tools:                          # List of tool module names
    - llm_tool
    - vision_tool
    - whisper_tool
    - media_tool
    - doc_tool
    - image_tool
    - data_tool
    - chart_tool
    - email_tool

# ─────────────────────────────────────────────────────────────────────────────
# INPUTS (Required)
# ─────────────────────────────────────────────────────────────────────────────
inputs:
  input_field_name:               # Field name (snake_case)
    type: string                  # Type: string, integer, number, boolean, array, object
    description: "Field desc"     # Human-readable description
    required: true                # Whether field is required (default: false)
    default: "default_value"      # Default value if not provided
    enum:                         # Allowed values (optional)
      - option1
      - option2
    minimum: 1                    # For integer/number: minimum value
    maximum: 100                  # For integer/number: maximum value
    pattern: "^[a-z]+$"           # For string: regex pattern

# ─────────────────────────────────────────────────────────────────────────────
# OUTPUTS (Required)
# ─────────────────────────────────────────────────────────────────────────────
outputs:
  output_field_name:              # Field name (snake_case)
    type: string                  # Type: string, integer, number, boolean, array, object
    description: "Field desc"     # Human-readable description

# ─────────────────────────────────────────────────────────────────────────────
# CLI CONFIGURATION (Required)
# ─────────────────────────────────────────────────────────────────────────────
cli:
  command: "praison recipes run my-recipe-name"  # Full command
  examples:                       # List of example invocations
    - 'praison recipes run my-recipe-name --input ''{"field": "value"}'''
    - 'praison recipes run my-recipe-name input.json --output ./out/'
    - 'praison recipes run my-recipe-name --dry-run'

# ─────────────────────────────────────────────────────────────────────────────
# SAFETY CONFIGURATION (Required)
# ─────────────────────────────────────────────────────────────────────────────
safety:
  dry_run_default: false          # If true, --write flag required to execute
  requires_consent: false         # If true, --consent flag required
  consent_message: ""             # Message shown when consent required
  legal_disclaimer: ""            # Legal text shown before execution
  overwrites_files: true          # Whether recipe may overwrite files
  network_access: true            # Whether recipe accesses network
  pii_handling: false             # Whether recipe handles PII data

4. Runtime Contract

Required Entrypoint

Every recipe must have a recipe.py with this signature:

python
def run(input_data: dict, config: dict = None) -> dict:
    """
    Execute the recipe.

    Args:
        input_data: Dictionary matching the inputs schema from TEMPLATE.yaml
        config: Optional configuration overrides

    Returns:
        Dictionary matching the outputs schema from TEMPLATE.yaml
        Must include 'ok' boolean for success/failure indication
    """
    pass

Standard Output Contract

python
# Success response
{
    "ok": True,
    "run_id": "run_abc123",           # Unique run identifier
    "recipe": "my-recipe-name",       # Recipe name
    "output": {                       # Tool-specific output
        "field1": "value1",
        "field2": 123
    },
    "artifacts": [                    # Generated files
        {"path": "output/file.txt", "type": "text", "size_bytes": 1024}
    ],
    "warnings": [],                   # Non-fatal warnings
    "error": None
}

# Error response
{
    "ok": False,
    "run_id": "run_abc123",
    "recipe": "my-recipe-name",
    "output": None,
    "artifacts": [],
    "warnings": [],
    "error": {
        "code": "MISSING_DEPENDENCY",
        "message": "ffmpeg not found",
        "details": "Install with: brew install ffmpeg",
        "retriable": False
    }
}

5. SDK Reference for Recipe Authors

Core Imports from praisonaiagents

python
# Agent - The primary agent class
from praisonaiagents import Agent

Agent(
    name="Agent Name",              # Required: Display name
    role="Role Description",        # Agent's role
    goal="What agent aims to do",   # Agent's goal
    instructions="...",             # System instructions
    llm="gpt-4o-mini",              # LLM model to use
    tools=[],                       # List of tools
                       # Enable verbose output
    reflection=False,             # Enable self-reflection
    max_iterations=3,                  # Max reflection iterations
    min_iterations=1,                  # Min reflection iterations
)

# Task - Define work for agents
from praisonaiagents import Task

Task(
    name="task_name",               # Task identifier
    description="What to do",       # Task description
    expected_output="Expected",     # What output should look like
    agent=agent,                    # Agent to execute task
    tools=[],                       # Task-specific tools
    context=[],                     # Context from other tasks
    async_execution=False,          # Run asynchronously
    output_file="output.txt",       # Save output to file
    output_json=MyModel,            # Pydantic model for output
)

# Agents - Orchestrator
from praisonaiagents import AgentTeam

agents = AgentTeam(
    agents=[agent1, agent2],        # List of agents
    tasks=[task1, task2],           # List of tasks
    process="sequential",           # "sequential" or "hierarchical"
                       # Enable verbose output
)
result = agents.start()             # Execute all tasks

# Tool decorator
from praisonaiagents import tool

@tool
def my_tool(param: str) -> str:
    """Tool description for LLM."""
    return f"Result: {param}"

Recipe Tools from praisonai-tools

python
# Install: pip install praisonai-tools

# LLM Tool - Unified LLM interface
from praisonai_tools.recipe_tools import LLMTool, llm_complete

llm = LLMTool(provider="openai", model="gpt-4o-mini")
response = llm.complete("prompt", system="system prompt", max_tokens=1000)
# response.content, response.model, response.usage

# Vision Tool - Image analysis
from praisonai_tools.recipe_tools import VisionTool, vision_caption

vision = VisionTool(provider="openai")
result = vision.analyze("image.jpg", prompt="Describe this")
# result.description, result.tags

# Chart Tool - Visualization
from praisonai_tools.recipe_tools import ChartTool, chart_bar

chart = ChartTool()
result = chart.bar({"A": 10, "B": 20}, title="Chart", output="chart.png")
# result.path

# Media Tool - Audio/Video
from praisonai_tools.recipe_tools import MediaTool, media_probe

media = MediaTool()
info = media.probe("video.mp4")
# info.duration, info.format, info.streams

# Whisper Tool - Transcription
from praisonai_tools.recipe_tools import WhisperTool, whisper_transcribe

whisper = WhisperTool()
transcript = whisper.transcribe("audio.mp3", language="en")
# transcript.text, transcript.segments

# Doc Tool - Document processing
from praisonai_tools.recipe_tools import DocTool, doc_extract_text

doc = DocTool()
text = doc.extract_text("document.pdf")

# Email Tool - Email parsing
from praisonai_tools.recipe_tools import EmailTool, email_parse

email = EmailTool()
parsed = email.parse("message.eml")
# parsed.sender, parsed.subject, parsed.body

# Data Tool - Data processing
from praisonai_tools.recipe_tools import DataTool, data_profile

data = DataTool()
profile = data.profile("data.csv")
# profile.columns, profile.row_count

# Check dependencies
deps = llm.check_dependencies()
# {"openai": True, "api_key": True}

6. CLI Reference

All Commands

bash
# List all recipes
praison recipes list
praison recipes list --json
praison recipes list --group

# Show recipe details
praison recipes info <recipe-name>
praison recipes info <recipe-name> --json

# Check dependencies
praison recipes doctor <recipe-name>

# Run a recipe
praison recipes run <recipe-name> <input>
praison recipes run <recipe-name> --input '{"key": "value"}'
praison recipes run <recipe-name> --input-file input.json
praison recipes run <recipe-name> --output ./output/
praison recipes run <recipe-name> --dry-run
praison recipes run <recipe-name> --write          # For dry-run-default recipes
praison recipes run <recipe-name> --consent        # For consent-required recipes
praison recipes run <recipe-name> --force          # Ignore missing deps
praison recipes run <recipe-name> --skip-checks    # Skip all checks
praison recipes run <recipe-name> --json           # JSON output

# Explain execution plan
praison recipes explain <recipe-name>

# Initialize recipe locally
praison recipes init <recipe-name>
praison recipes init <recipe-name> --output ./my-copy/
praison recipes init <recipe-name> --force
praison recipes init <recipe-name> --dry-run

CLI Flags Reference

FlagCommandsDescription
--jsonlist, info, runOutput as JSON
--grouplistGroup recipes by category
--inputrunJSON input string
--input-filerunPath to JSON input file
--output, -orun, initOutput directory
--dry-runrun, initShow what would be done
--writerunExecute dry-run-default recipes
--consentrunAcknowledge consent requirements
--forcerun, initForce execution/overwrite
--skip-checksrunSkip dependency checks

7. Testing Patterns

python
# test_recipe.py
import pytest
from recipe import run

# Unit test - fast, no API calls
def test_input_validation():
    """Test that missing required fields raise errors."""
    with pytest.raises(ValueError):
        run({})

def test_output_structure():
    """Test output has required fields."""
    result = run({"input": "test"})
    assert "ok" in result
    assert "output" in result

# Integration test - with real APIs
@pytest.mark.integration
def test_with_real_api():
    """Test with actual API calls."""
    result = run({"input": "real data"})
    assert result["ok"] is True

# Mark slow tests
@pytest.mark.slow
def test_large_file_processing():
    """Test with large files."""
    pass

# Run tests
# pytest test_recipe.py                    # Unit tests only
# pytest test_recipe.py -m integration     # Integration tests
# pytest test_recipe.py -m "not slow"      # Skip slow tests

8. Distribution & Packaging

yaml
# TEMPLATE.yaml metadata for distribution
name: my-recipe
version: "1.0.0"
author: "Your Name <email@example.com>"
license: "MIT"                    # SPDX identifier
repository: "https://github.com/user/repo"
homepage: "https://example.com"
keywords:
  - ai
  - automation

To share a recipe:

  1. Create a Git repository with the recipe directory structure
  2. Users clone/copy to ~/.praisonai/templates/
  3. Or publish to a recipe registry (future feature)

9. Troubleshooting Matrix

IssueCauseSolution
Recipe not discoveredWrong locationMove to ~/.praisonai/templates/
Recipe not discoveredMissing TEMPLATE.yamlCreate valid TEMPLATE.yaml
Missing dependencyPackage not installedpip install <package>
Missing dependencyExternal tool missingInstall system tool (ffmpeg, tesseract)
Env var missingNot setexport VAR_NAME=value
Tool import failurepraisonai-tools not installedpip install praisonai-tools
Runtime errorInvalid inputCheck input against schema
Dry-run modedry_run_default=trueUse --write flag
Consent requiredrequires_consent=trueUse --consent flag

10. AI Recipe Author Prompt

Use this prompt to instruct an AI to create a valid recipe:

You are creating a PraisonAI recipe. Follow these rules exactly:

1. Create a directory with the recipe name (kebab-case)
2. Create TEMPLATE.yaml with ALL required fields:
   - name, version, description, author, license
   - tags (at least one)
   - requires (env, packages, tools as needed)
   - inputs (with type, description, required for each)
   - outputs (with type, description for each)
   - cli (command and examples)
   - safety (dry_run_default, requires_consent, overwrites_files)

3. Create recipe.py with:
   - def run(input_data: dict, config: dict = None) -> dict
   - Validate all required inputs
   - Return dict with 'ok', 'output', 'artifacts', 'warnings', 'error'
   - Handle errors gracefully

4. Create README.md with:
   - Title and description
   - Quick start example
   - Inputs table
   - Outputs table
   - Requirements
   - Troubleshooting

5. Create test_recipe.py with:
   - Unit tests for input validation
   - Integration tests marked with @pytest.mark.integration

6. Use praisonaiagents for Agent, Task, Agents
7. Use praisonai_tools.recipe_tools for LLMTool, VisionTool, etc.
8. Always check dependencies before using tools
9. Use semantic versioning for version field
10. Include proper error handling and logging

Source Files Reference:

  • CLI: praisonai/cli/features/recipes.py
  • Tools: praisonai_tools/recipe_tools/__init__.py
  • Core SDK: praisonaiagents/__init__.py
  • Templates: Agent-Recipes/agent_recipes/templates/*/TEMPLATE.yaml