Skip to main content

Model 3: Local HTTP Sidecar

When to Use: Microservices architectures, non-Python applications, or when you need HTTP-based integration without deploying to the cloud.

How It Works

The sidecar runs as a local HTTP server, exposing recipes via REST API. Your application communicates over localhost—no external network required.

Pros & Cons

  • Polyglot - Any language with HTTP client can use it
  • Process isolation - Recipes run in separate process
  • Standard HTTP - Familiar REST/JSON interface
  • Streaming support - SSE for real-time events
  • Auth built-in - API key and JWT authentication
  • Hot reload - Update recipes without restart

Step-by-Step Tutorial

1

Install with Serve Dependencies

pip install "praisonai[serve]"
2

Start the Sidecar Server

# Basic start
praisonai recipe serve --port 8765

# With API key authentication
praisonai recipe serve --port 8765 --auth api-key

# With hot reload for development
praisonai recipe serve --port 8765 --reload
3

Verify Server Health

curl http://localhost:8765/health
Expected response:
{"status": "healthy", "service": "praisonai-recipe-runner", "version": "..."}
4

List Available Recipes

curl http://localhost:8765/v1/recipes
5

Run a Recipe

curl -X POST http://localhost:8765/v1/recipes/run \
  -H "Content-Type: application/json" \
  -d '{
    "recipe": "my-recipe",
    "input": {"query": "Hello"}
  }'

API Reference

EndpointMethodDescription
/healthGETHealth check
/v1/recipesGETList recipes
/v1/recipes/{name}GETDescribe recipe
/v1/recipes/{name}/schemaGETGet recipe schema
/v1/recipes/runPOSTRun recipe (sync)
/v1/recipes/streamPOSTRun recipe (SSE)
/v1/recipes/validatePOSTValidate recipe

Production-Ready Example

import requests
from typing import Any, Dict, Optional

class RecipeClient:
    """HTTP client for PraisonAI recipe sidecar."""
    
    def __init__(
        self,
        base_url: str = "http://localhost:8765",
        api_key: Optional[str] = None,
        timeout: int = 60
    ):
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.headers = {"Content-Type": "application/json"}
        if api_key:
            self.headers["X-API-Key"] = api_key
    
    def health(self) -> Dict[str, Any]:
        """Check server health."""
        resp = requests.get(
            f"{self.base_url}/health",
            headers=self.headers,
            timeout=5
        )
        resp.raise_for_status()
        return resp.json()
    
    def list_recipes(self, tags: list = None) -> list:
        """List available recipes."""
        params = {}
        if tags:
            params["tags"] = ",".join(tags)
        
        resp = requests.get(
            f"{self.base_url}/v1/recipes",
            headers=self.headers,
            params=params,
            timeout=self.timeout
        )
        resp.raise_for_status()
        return resp.json().get("recipes", [])
    
    def run(
        self,
        recipe_name: str,
        input_data: Dict[str, Any],
        config: Dict[str, Any] = None,
        session_id: str = None
    ) -> Dict[str, Any]:
        """Run a recipe."""
        body = {
            "recipe": recipe_name,
            "input": input_data,
        }
        if config:
            body["config"] = config
        if session_id:
            body["session_id"] = session_id
        
        resp = requests.post(
            f"{self.base_url}/v1/recipes/run",
            headers=self.headers,
            json=body,
            timeout=self.timeout
        )
        resp.raise_for_status()
        return resp.json()
    
    def run_stream(self, recipe_name: str, input_data: Dict[str, Any]):
        """Run a recipe with streaming."""
        body = {"recipe": recipe_name, "input": input_data}
        
        with requests.post(
            f"{self.base_url}/v1/recipes/stream",
            headers={**self.headers, "Accept": "text/event-stream"},
            json=body,
            stream=True,
            timeout=self.timeout
        ) as resp:
            resp.raise_for_status()
            for line in resp.iter_lines():
                if line:
                    line = line.decode("utf-8")
                    if line.startswith("data:"):
                        yield line[5:].strip()


# Usage
if __name__ == "__main__":
    client = RecipeClient(api_key="your-api-key")
    
    # Check health
    print(client.health())
    
    # Run recipe
    result = client.run(
        "support-reply-drafter",
        {"ticket_id": "T-123", "message": "I need help"}
    )
    print(result["output"])

Docker Deployment

FROM python:3.11-slim

WORKDIR /app

RUN pip install "praisonai[serve]"

EXPOSE 8765

CMD ["praisonai", "recipe", "serve", "--host", "0.0.0.0", "--port", "8765"]
# docker-compose.yml
version: '3.8'
services:
  praisonai-sidecar:
    build: .
    ports:
      - "8765:8765"
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - PRAISONAI_API_KEY=${PRAISONAI_API_KEY}
    volumes:
      - ./recipes:/root/.praison/templates
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8765/health"]
      interval: 30s
      timeout: 10s
      retries: 3

Troubleshooting

Ensure the server is running:
# Check if port is in use
lsof -i :8765

# Start server
praisonai recipe serve --port 8765
If auth is enabled, provide the API key:
curl -H "X-API-Key: your-key" http://localhost:8765/v1/recipes
Or set the environment variable:
export PRAISONAI_API_KEY=your-key
Ensure you’re using the correct endpoint and headers:
curl -X POST http://localhost:8765/v1/recipes/stream \
  -H "Accept: text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"recipe": "my-recipe", "input": {}}'
Start the server with CORS enabled:
# Via environment
export PRAISONAI_CORS_ORIGINS="*"
praisonai recipe serve

# Or via config file (serve.yaml)
# cors_origins: "*"

Security & Ops Notes

Security Considerations
  • Bind to localhost - Default 127.0.0.1 prevents external access
  • API key auth - Enable --auth api-key for production
  • HTTPS - Use a reverse proxy (nginx, traefik) for TLS
  • Rate limiting - Configure rate_limit in serve.yaml
  • Request size limits - Default 10MB, adjust via max_request_size
# serve.yaml - Production configuration
host: 127.0.0.1
port: 8765
auth: api-key
api_key: ${PRAISONAI_API_KEY}
cors_origins: "https://myapp.com"
rate_limit: 100
max_request_size: 10485760
enable_metrics: true

Next Steps