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.")
Role: Design, build, test, and document recipes that solve real-world problems for App Developers and end users.
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
All Integration Models
Understand how recipes are consumed
Use Cases
Learn from existing patterns
Decision Guide
Help users choose the right approach
App Developer Persona
Understand your users
Typical Workflow
1
Define the Problem
# 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
# Initialize recipe directory
mkdir -p ~/.praisonai/templates/support-reply-drafter
cd ~/.praisonai/templates/support-reply-drafter
# 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
# 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
# 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
# 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
# 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
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
# 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
Recipe naming
✅ Good names:
- support-reply-drafter
- code-review-assistant
- document-summarizer
❌ Bad names:
- my-recipe
- test123
- agent
Version management
Version management
# Use semantic versioning
version: "1.2.3"
# 1 = major (breaking changes)
# 2 = minor (new features)
# 3 = patch (bug fixes)
Dependency management
Dependency management
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
Recipe not discovered
Check recipe location:
# Recipes are discovered from:
ls ~/.praisonai/templates/
ls ~/.config/praison/templates/
ls ./.praison/templates/
Tests failing in CI
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
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
- Integration Models - How recipes are consumed
- Use Cases - Inspiration for new recipes
- App Developer Persona - Understand your users
SDK & Recipe Author Reference (Copy/Paste for AI)
This section is designed to be copied wholesale and given to an AI assistant. It contains everything needed to build a complete, valid PraisonAI recipe from scratch.
1. Recipe Discovery
Recipes are discovered from these directories in order of precedence (first match wins):| Priority | Path | Description |
|---|---|---|
| 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 |
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
# ═══════════════════════════════════════════════════════════════════════════
# 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 arecipe.py with this signature:
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
# 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
# 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
# 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
# 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
| Flag | Commands | Description |
|---|---|---|
--json | list, info, run | Output as JSON |
--group | list | Group recipes by category |
--input | run | JSON input string |
--input-file | run | Path to JSON input file |
--output, -o | run, init | Output directory |
--dry-run | run, init | Show what would be done |
--write | run | Execute dry-run-default recipes |
--consent | run | Acknowledge consent requirements |
--force | run, init | Force execution/overwrite |
--skip-checks | run | Skip dependency checks |
7. Testing Patterns
# 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
# 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
- Create a Git repository with the recipe directory structure
- Users clone/copy to
~/.praisonai/templates/ - Or publish to a recipe registry (future feature)
9. Troubleshooting Matrix
| Issue | Cause | Solution |
|---|---|---|
| Recipe not discovered | Wrong location | Move to ~/.praisonai/templates/ |
| Recipe not discovered | Missing TEMPLATE.yaml | Create valid TEMPLATE.yaml |
| Missing dependency | Package not installed | pip install <package> |
| Missing dependency | External tool missing | Install system tool (ffmpeg, tesseract) |
| Env var missing | Not set | export VAR_NAME=value |
| Tool import failure | praisonai-tools not installed | pip install praisonai-tools |
| Runtime error | Invalid input | Check input against schema |
| Dry-run mode | dry_run_default=true | Use --write flag |
| Consent required | requires_consent=true | Use --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

