> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Recipe Serve (Python)

> Programmatic server configuration and management using Python

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(name="code-recipe-agent", instructions="Execute code-based recipes.")
agent.start("Run the code-generation recipe to scaffold a new FastAPI project.")
```

The user hits the recipe server over HTTP; Python `serve()` config controls auth, CORS, and ports.

Expose recipe workflows over HTTP — configure auth, CORS, and deployment from Python or YAML.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai.recipe.serve import serve

serve(host="127.0.0.1", port=8765)
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Recipe Serve"
        Config["⚙️ serve.yaml"] --> Server["🖥️ Recipe Server"]
        Code["🐍 Python"] --> Server
        Server --> HTTP["🌐 HTTP"]
        HTTP --> Client["📱 Client"]
        Server --> Auth["🔐 Auth"]
    end

    classDef config fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef server fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef protocol fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef client fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef auth fill:#10B981,stroke:#7C90A0,color:#fff

    class Config,Code config
    class Server server
    class HTTP protocol
    class Client client
    class Auth auth
```

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant RecipeServePython

    User->>Agent: Request
    Agent->>RecipeServePython: Process
    RecipeServePython-->>Agent: Result
    Agent-->>User: Response
```

## Quick Start

<Steps>
  <Step title="Simple Usage">
    Start a local recipe server:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai.recipe.serve import serve

    serve(host="127.0.0.1", port=8765)
    ```

    Or via CLI:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai recipe serve --host 127.0.0.1 --port 8765
    ```
  </Step>

  <Step title="With Configuration">
    Load settings from `serve.yaml` and override auth:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import os
    from praisonai.recipe.serve import serve, load_config

    config = load_config("serve.yaml")
    config["auth"] = "api-key"
    config["api_key"] = os.environ["PRAISONAI_API_KEY"]

    serve(
        host=config.get("host", "127.0.0.1"),
        port=config.get("port", 8765),
        config=config,
    )
    ```
  </Step>
</Steps>

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant Feature as Recipe Serve (Python)

    User->>Agent: Request
    Agent->>Feature: Process request
    Feature-->>Agent: Result
    Agent-->>User: Response
```

`create_app()` builds a Starlette ASGI application; `serve()` runs it with uvicorn. Configuration merges file, environment variables, and Python overrides — CLI flags take highest precedence.

| Route | Method | Description |
| - | - | - |
| `/health` | GET | Health check |
| `/v1/recipes` | GET | List recipes |
| `/v1/recipes/{name}` | GET | Describe recipe |
| `/v1/recipes/run` | POST | Run recipe (sync) |
| `/v1/recipes/stream` | POST | Run recipe (SSE) |

***

## Configuration Options

| Key | Type | Default | Description |
| - | - | - | - |
| `host` | `str` | `"127.0.0.1"` | Bind address |
| `port` | `int` | `8765` | Listen port |
| `auth` | `str` | `"none"` | Auth mode (`none`, `api-key`, `jwt`) |
| `api_key` | `str` | env | API key when `auth=api-key` |
| `recipes` | `list` | all | Recipe names to expose |
| `preload` | `bool` | `false` | Warm recipes on startup |
| `cors_origins` | `str` | `"*"` | Allowed CORS origins |
| `log_level` | `str` | `"info"` | Server log level |

### serve.yaml example

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
host: 127.0.0.1
port: 8765
auth: api-key
recipes:
  - support-reply-drafter
  - meeting-action-items
preload: true
cors_origins: "https://app.example.com"
```

Set `PRAISONAI_API_KEY` in the environment instead of hardcoding keys.

***

## Concurrency model

The recipe server keeps its event loop responsive while long recipes run.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Non-Blocking Execution"
        Req["📥 recipe.*"] --> Thread["🧵 asyncio.to_thread"]
        Thread --> Loop["🔁 Event loop free"]
        Loop --> Health["💚 /health · /metrics"]
    end

    classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class Req input
    class Thread,Loop process
    class Health output
```

Every `recipe.*` call (`list_recipes`, `describe`, `get_schema`, `run`, `validate`, `admin_reload`) is dispatched through `asyncio.to_thread`. A long-running recipe no longer blocks the single event loop — `/health`, `/metrics`, and every other request keep responding during execution.

SSE streaming (`POST /v1/recipes/stream`) drives the underlying sync generator on a worker thread and hands each event back to the loop through an `asyncio.Queue` (max 64 items). Events reach the client without any `next()` blocking the loop, and the worker is signalled and drained cleanly on client disconnect so no threads leak.

For operators: a single uvicorn worker can serve mixed short and long recipes without starving readiness probes. You no longer need a separate process per long recipe to keep the health check responsive.

<Note>
  Non-blocking dispatch applies as of PraisonAI #4215. Sync recipe work is offloaded to a worker thread pool and SSE events are queued off the event loop.
</Note>

***

## Streaming

`praisonai serve recipe` streams progressively when fronted by a gateway channel — the same DraftStreamer edits and SSE chunks a Python-`Agent`-backed bot produces.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Channel as 📱 Channel
    participant Gateway as ⚙️ Gateway
    participant Adapter as 🔌 RecipeBotAdapter
    participant Recipe as 📝 Recipe (YAML)

    User->>Channel: message
    Channel->>Gateway: inbound
    Gateway->>Adapter: astart(msg, stream=True)
    Adapter->>Recipe: run_stream(...)
    Recipe-->>Adapter: progress event
    Adapter-->>Gateway: TOOL_PROGRESS (status phrase)
    Recipe-->>Adapter: output event
    Adapter-->>Gateway: FIRST_TOKEN + DELTA_TEXT
    Gateway-->>Channel: draft edit / SSE chunk
    Recipe-->>Adapter: end
    Adapter-->>Gateway: STREAM_END
    Gateway-->>Channel: finalised reply
    Channel-->>User: progressive reply
```

The adapter maps each recipe event to a stream event the channel renders:

| Recipe event | Stream event emitted | Drives |
| - | - | - |
| `progress` (with `message`) | `TOOL_PROGRESS` | Status / activity phrases during the turn |
| `output` (with `output`) | `FIRST_TOKEN` + `DELTA_TEXT` | DraftStreamer edits + SSE chunks |
| end of iterator | `STREAM_END` | Finalises the draft |
| `error` | raises (`STREAM_END` still emitted) | Falls back to failure-reply text |

Subscribe to the adapter's `stream_emitter` and drive it with the async surface:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import asyncio
from praisonaiagents.gateway.adapters.recipe_adapter import RecipeBotAdapter

adapter = RecipeBotAdapter("my_recipe")

async def on_event(event):
    print(event.type, getattr(event, "content", ""))

adapter.stream_emitter.add_callback(on_event)

async def main():
    final = await adapter.astart("hello", stream=True)
    print(final)

asyncio.run(main())
```

<Note>
  No new config knob was added — enabling streaming on the gateway side is enough; the recipe surface participates automatically. When nothing is listening on `stream_emitter`, the adapter still runs the recipe and returns the final text with no events emitted.
</Note>

***

## Common Patterns

### ASGI app for custom deployment

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai.recipe.serve import create_app
import uvicorn

app = create_app(config={"auth": "none", "cors_origins": "*"})
uvicorn.run(app, host="0.0.0.0", port=8765)
```

### Unified agents.yaml config

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
serve:
  host: 127.0.0.1
  port: 8765
  auth: api-key
  preload: true
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai.recipe.serve import load_config, serve

config = load_config("agents.yaml").get("serve", {})
serve(host=config.get("host", "127.0.0.1"), port=config.get("port", 8765), config=config)
```

### Test with TestClient

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os
from starlette.testclient import TestClient
from praisonai.recipe.serve import create_app

app = create_app(config={"auth": "api-key", "api_key": "test-key"})
client = TestClient(app)

assert client.get("/health").json()["status"] == "healthy"
assert client.get("/v1/recipes", headers={"X-API-Key": "test-key"}).status_code == 200
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Always use auth in production">
    Set `auth: api-key` and load the key from `PRAISONAI_API_KEY`. Never expose an unauthenticated recipe server on a public network.
  </Accordion>

  <Accordion title="Preload recipes for faster startup">
    Set `preload: true` to warm recipes at startup and eliminate cold-start latency on the first request.
  </Accordion>

  <Accordion title="Restrict CORS origins">
    Set `cors_origins` to your exact frontend domain in production rather than `"*"`.
  </Accordion>

  <Accordion title="Poll /health for orchestration">
    Use the `/health` endpoint in Docker, Kubernetes, and load-balancer health checks.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Recipe Serve Advanced" icon="gauge-high" href="/docs/features/recipe-serve-advanced">
    Rate limiting, metrics, admin reload, and OpenTelemetry
  </Card>

  <Card title="Endpoints Code" icon="code" href="/docs/features/endpoints-code">
    Client-side code for calling recipe endpoints
  </Card>

  <Card title="Bot Streaming Replies" icon="message-pen" href="/docs/features/bot-streaming-replies#works-with-recipeyaml-backed-agents-too">
    Stream recipe-backed gateway agents with DraftStreamer edits and SSE chunks
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.