Model 2: CLI Invocation (Subprocess)
When to Use : Shell scripts, CI/CD pipelines, batch processing, or when you need language-agnostic recipe invocation without running a server.
How It Works
The CLI model spawns a subprocess to run recipes. Output is captured via stdout in JSON format for easy parsing.
Pros & Cons
Language-agnostic - Works from any language that can spawn processes
Simple JSON output - Easy to parse in any language
No SDK dependency - Calling app doesn’t need praisonai installed
Process isolation - Recipe runs in separate process
Easy debugging - Run commands manually to test
Process spawn overhead - ~100-500ms per invocation
Stdout/stderr parsing - Need to handle output parsing
No streaming - Unless using --stream flag
Environment setup - CLI must be in PATH
Step-by-Step Tutorial
List Available Recipes
# Human-readable format
praisonai recipe list
# JSON format for parsing
praisonai recipe list --json
Get Recipe Info
praisonai recipe info my-recipe --json
Run Recipe with JSON Output
praisonai recipe run my-recipe \
--input '{"query": "Hello"}' \
--json
Parse Output in Your Application
import subprocess
import json
result = subprocess.run(
[ "praisonai" , "recipe" , "run" , "my-recipe" ,
"--input" , '{"query": "Hello"}' , "--json" ],
capture_output = True ,
text = True ,
timeout = 60
)
if result.returncode == 0 :
data = json.loads(result.stdout)
print ( f "Run ID: { data[ 'run_id' ] } " )
print ( f "Output: { data[ 'output' ] } " )
else :
print ( f "Error: { result.stderr } " )
Production-Ready Example
import subprocess
import json
import logging
from typing import Any, Dict, Optional
logging.basicConfig( level = logging. INFO )
logger = logging.getLogger( __name__ )
class RecipeCLIClient :
"""Production-ready CLI client for recipe invocation."""
def __init__ ( self , timeout : int = 60 , retries : int = 3 ):
self .timeout = timeout
self .retries = retries
def run (
self ,
recipe_name : str ,
input_data : Dict[ str , Any],
dry_run : bool = False
) -> Dict[ str , Any]:
"""Run a recipe via CLI with retries."""
cmd = [
"praisonai" , "recipe" , "run" , recipe_name,
"--input" , json.dumps(input_data),
"--json"
]
if dry_run:
cmd.append( "--dry-run" )
last_error = None
for attempt in range ( self .retries):
try :
result = subprocess.run(
cmd,
capture_output = True ,
text = True ,
timeout = self .timeout
)
if result.returncode == 0 :
data = json.loads(result.stdout)
logger.info( f "Recipe { recipe_name } completed: { data.get( 'run_id' ) } " )
return data
else :
logger.warning( f "Recipe failed (attempt { attempt + 1 } ): { result.stderr } " )
last_error = result.stderr
except subprocess.TimeoutExpired:
logger.error( f "Recipe timeout (attempt { attempt + 1 } )" )
last_error = "Timeout"
except json.JSONDecodeError as e:
logger.error( f "JSON parse error: { e } " )
last_error = str (e)
raise RuntimeError ( f "Recipe { recipe_name } failed: { last_error } " )
def list_recipes ( self ) -> list :
"""List available recipes."""
result = subprocess.run(
[ "praisonai" , "recipe" , "list" , "--json" ],
capture_output = True ,
text = True ,
timeout = 30
)
if result.returncode == 0 :
return json.loads(result.stdout)
return []
# Usage
if __name__ == "__main__" :
client = RecipeCLIClient( timeout = 60 , retries = 3 )
# List recipes
recipes = client.list_recipes()
print ( f "Found { len (recipes) } recipes" )
# Run a recipe
result = client.run(
"support-reply-drafter" ,
{ "ticket_id" : "T-123" , "message" : "I need help" }
)
print (result[ "output" ])
CI/CD Integration
GitHub Actions
name : Run Recipe
on : [ push ]
jobs :
run-recipe :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- name : Set up Python
uses : actions/setup-python@v5
with :
python-version : '3.11'
- name : Install PraisonAI
run : pip install praisonai
- name : Run Recipe
env :
OPENAI_API_KEY : ${{ secrets.OPENAI_API_KEY }}
run : |
praisonai recipe run code-review \
--input '{"diff": "..."}' \
--json > result.json
- name : Upload Result
uses : actions/upload-artifact@v4
with :
name : recipe-result
path : result.json
GitLab CI
run-recipe :
image : python:3.11
script :
- pip install praisonai
- praisonai recipe run my-recipe --input '{"query" : "test" } ' --json
variables:
OPENAI_API_KEY: $OPENAI_API_KEY
Troubleshooting
Command not found: praisonai
Add praisonai to your PATH or use the full path: # Find installation path
python -c "import praisonai; print(praisonai.__file__)"
# Or use python -m
python -m praisonai recipe run my-recipe --json
Ensure you’re using the --json flag and only parsing stdout: result = subprocess.run(cmd, capture_output = True , text = True )
# Only parse stdout, not stderr
data = json.loads(result.stdout)
Check stderr for error details: if result.returncode != 0 :
print ( f "Exit code: { result.returncode } " )
print ( f "Error: { result.stderr } " )
Common exit codes:
1: General error
2: Validation error
7: Recipe not found
Environment variables not passed
Explicitly pass environment variables: import os
result = subprocess.run(
cmd,
capture_output = True ,
text = True ,
env = { ** os.environ, "OPENAI_API_KEY" : "sk-..." }
)
Security & Ops Notes
Input sanitization : Never pass unsanitized user input directly to CLI
Shell injection : Use list form of subprocess.run, not shell=True
API keys : Pass via environment variables, not command line arguments
Timeouts : Always set timeouts to prevent hanging processes
# GOOD: List form, env vars
subprocess.run(
[ "praisonai" , "recipe" , "run" , name, "--input" , json.dumps(data)],
env = { "OPENAI_API_KEY" : key}
)
# BAD: Shell form, key in command
subprocess.run(
f "OPENAI_API_KEY= { key } praisonai recipe run { name } " ,
shell = True # Dangerous!
)
CLI Reference
praisonai recipe run < nam e > [options]
Options:
--input < jso n > Input data as JSON string
--output < di r > Output directory
--json Output as JSON
--dry-run Show what would be done
--write Execute (for dry-run-default recipes )
--force Force execution despite missing deps
--consent Acknowledge consent requirements
--skip-checks Skip dependency checks
Next Steps