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