PraisonAIUI uses a provider protocol — a simple abstraction that lets you plug in any AI backend.
The provider protocol is the only contract between the frontend and any AI backend. All dashboard pages (/api/overview, /api/config, /api/logs, etc.) are provider-agnostic — they always work regardless of which backend is active. The provider only handles agent execution (run()), agent listing (list_agents()), and health checks (health()).
/api/overview
/api/config
/api/logs
run()
list_agents()
health()
1. User sends a message → POST /run
POST /run
2. Server calls provider.run(message) on the active provider
provider.run(message)
3. Provider yields RunEvent objects (start, content, tool calls, reasoning, completion)
RunEvent
4. Server serialises each RunEvent to an SSE data: frame
data:
5. Frontend useSSE.ts parses events and renders components
useSSE.ts
PraisonAI is the default provider. It wraps the @aiui.reply callback system:
@aiui.reply
No extra config needed — PraisonAIProvider is used automatically.
PraisonAIProvider
The default provider resolves tools for agents using ToolResolver from the praisonai package. Agents created via the CRUD API or ~/.praisonaiui/config.yaml get their tool names (strings) automatically resolved to callable Python functions:
ToolResolver
praisonai
~/.praisonaiui/config.yaml
When no tools are configured, the provider gives agents sensible defaults (internet_search).
internet_search
Agents also support reflection: true (default) for self-reflection mode, where the agent evaluates its own response quality before returning.
reflection: true
Implement BaseProvider.run() — that's it:
BaseProvider.run()
Any provider can attach rich media to chat messages using `MESSAGE_ELEMENT`
MESSAGE_ELEMENT
events. The UI renders them inline and persists them in session history.
Tool results from PraisonAI agents are also detected automatically when they
match OpenAI image shapes (`{"data": [{"url": "..."}]}`) or explicit element
{"data": [{"url": "..."}]}
dicts — no provider code required.
Callback handlers can use `Message.add_image() or await aiui.image(url)`
Message.add_image()
or
await aiui.image(url)
and the default `PraisonAIProvider maps those to MESSAGE_ELEMENT` events.
maps those to
Full architecture, data flows, and tests: Image Preview in Chat.
Every event has a type field and optional payload fields:
type
| Field | Type | Used By |
|-------|------|---------|
| type | RunEventType | All events |
RunEventType
| content | str | RUN_COMPLETED, RUN_CONTENT |
content
str
RUN_COMPLETED
RUN_CONTENT
| token | str | RUN_CONTENT (streaming) |
token
| name | str | TOOL_CALL_* |
name
TOOL_CALL_*
| args | dict | TOOL_CALL_STARTED |
args
dict
TOOL_CALL_STARTED
| result | any | TOOL_CALL_COMPLETED |
result
any
TOOL_CALL_COMPLETED
| step | str | REASONING_STEP |
step
REASONING_STEP
| error | str | RUN_ERROR |
error
RUN_ERROR
| agent_name | str | Multi-agent events |
agent_name
| extra_data | dict | Custom payload (includes element for message_element) |
extra_data
element
message_element
=== "Agent Events"
| Type | Description |
|------|-------------|
| run_started | Agent run begins |
run_started
| run_content | Streaming token or content chunk |
run_content
| run_completed | Agent run finished |
run_completed
| run_error | Error occurred |
run_error
| run_cancelled | User cancelled |
run_cancelled
| tool_call_started | Tool invocation started |
tool_call_started
| tool_call_completed | Tool returned result |
tool_call_completed
| reasoning_started | Thinking begun |
reasoning_started
| reasoning_step | Individual reasoning step |
reasoning_step
| reasoning_completed | Thinking finished |
reasoning_completed
| memory_update_started | Memory write begun |
memory_update_started
| memory_update_completed | Memory write done |
memory_update_completed
| updating_memory | Memory being updated |
updating_memory
| run_paused | Run paused (e.g. waiting for user) |
run_paused
| run_continued | Run resumed |
run_continued
| message_element | Inline media element (image, video, file, etc.) — see Image Preview in Chat |
=== "Team Events"
| team_run_started | Team run begins |
team_run_started
| team_run_content | Team streaming content |
team_run_content
| team_run_completed | Team run finished |
team_run_completed
| team_run_error | Team error |
team_run_error
| team_run_cancelled | Team cancelled |
team_run_cancelled
| team_tool_call_started | Team tool call started |
team_tool_call_started
| team_tool_call_completed | Team tool call done |
team_tool_call_completed
| team_reasoning_started | Team reasoning begun |
team_reasoning_started
| team_reasoning_step | Team reasoning step |
team_reasoning_step
| team_reasoning_completed | Team reasoning done |
team_reasoning_completed
| team_memory_update_started | Team memory update started |
team_memory_update_started
| team_memory_update_completed | Team memory update done |
team_memory_update_completed
: Subclass and implement run() to create a custom provider.
| Method | Description |
|--------|-------------|
| run(message, *, session_id, agent_name, kw) | Required.** Async generator yielding RunEvent objects |
run(message, *, session_id, agent_name, kw)
| list_agents() | Optional. Returns list of available agents |
| health() | Optional. Returns health check dict |
: Structured event with type: RunEventType and optional payload fields (see table above).
type: RunEventType
to_dict()
SSEEvent
: 27 string values matching the frontend types.ts — see Agent Events and Team Events tabs above.
types.ts
| Function | Description |
|----------|-------------|
| praisonaiui.set_provider(provider) | Set the active provider |
praisonaiui.set_provider(provider)
| praisonaiui.get_provider() | Get the active provider (lazy-inits PraisonAIProvider) |
praisonaiui.get_provider()