A minimal library to make Chainlit easier to use.
Project description
Easierlit
Easierlit is a Python-first wrapper around Chainlit. It keeps the power of Chainlit while reducing the boilerplate for worker loops, message flow, auth, and persistence.
Quick Links
- Installation: Install
- Start in 60 seconds: Quick Start
- Method contracts:
docs/api-reference.en.md - Full usage guide:
docs/usage.en.md - Korean docs:
README.ko.md,docs/api-reference.ko.md,docs/usage.ko.md
Why Easierlit
- Clear runtime split:
EasierlitServer: runs Chainlit in the main process.EasierlitClient: dispatches incoming messages toon_message(app, incoming)workers.EasierlitApp: message/thread CRUD bridge for outgoing commands.- Production-oriented defaults:
- headless server mode
- sidebar default state
open - JWT secret auto-management (
.chainlit/jwt.secret) - scoped auth cookie default (
easierlit_access_token_<hash>) - fail-fast worker policy
- Practical persistence behavior:
- default SQLite bootstrap (
.chainlit/easierlit.db) - schema compatibility recovery
- SQLite
tagsnormalization for thread CRUD
Architecture at a Glance
User UI
-> Chainlit callbacks (on_message / on_chat_start / ...)
-> Easierlit runtime bridge
-> EasierlitClient incoming dispatcher
-> on_message(app, incoming) in per-message workers (thread)
-> app.* APIs (message + thread CRUD)
-> runtime dispatcher
-> realtime session OR data-layer fallback
Install
pip install easierlit
For local development:
pip install -e ".[dev]"
Quick Start (60 Seconds)
from easierlit import EasierlitClient, EasierlitServer
def on_message(app, incoming):
app.add_message(
thread_id=incoming.thread_id,
content=f"Echo: {incoming.content}",
author="EchoBot",
)
client = EasierlitClient(on_message=on_message)
server = EasierlitServer(client=client)
server.serve() # blocking
Optional background run_func pattern:
import time
from easierlit import EasierlitClient, EasierlitServer
def on_message(app, incoming):
app.add_message(incoming.thread_id, f"Echo: {incoming.content}", author="EchoBot")
def run_func(app):
while not app.is_closed():
# Optional background worker; no inbound message polling.
time.sleep(0.2)
client = EasierlitClient(
on_message=on_message,
run_funcs=[run_func], # optional
run_func_mode="auto", # auto/sync/async
)
server = EasierlitServer(client=client)
server.serve()
Image element example (without Markdown):
from chainlit.element import Image
image = Image(name="diagram.png", path="/absolute/path/diagram.png")
app.add_message(
thread_id=incoming.thread_id,
content="Attached image",
elements=[image],
)
External in-process enqueue example:
message_id = app.enqueue(
thread_id="thread-external",
content="hello from external integration",
session_id="webhook-1",
author="Webhook",
)
Public API
EasierlitServer(
client,
host="127.0.0.1",
port=8000,
root_path="",
max_outgoing_workers=4,
auth=None,
persistence=None,
discord=None,
)
EasierlitClient(
on_message,
run_funcs=None,
worker_mode="thread",
run_func_mode="auto",
max_message_workers=64,
)
EasierlitApp.discord_typing_open(thread_id) -> bool
EasierlitApp.discord_typing_close(thread_id) -> bool
EasierlitApp.enqueue(thread_id, content, session_id="external", author="User", message_id=None, metadata=None, elements=None, created_at=None) -> str
EasierlitApp.add_message(thread_id, content, author="Assistant", metadata=None, elements=None) -> str
EasierlitApp.add_tool(thread_id, tool_name, content, metadata=None, elements=None) -> str
EasierlitApp.add_thought(thread_id, content, metadata=None, elements=None) -> str # tool_name is fixed to "Reasoning"
EasierlitApp.send_to_discord(thread_id, content, elements=None) -> bool
EasierlitApp.is_discord_thread(thread_id) -> bool
EasierlitApp.update_message(thread_id, message_id, content, metadata=None, elements=None)
EasierlitApp.update_tool(thread_id, message_id, tool_name, content, metadata=None, elements=None)
EasierlitApp.update_thought(thread_id, message_id, content, metadata=None, elements=None) # tool_name is fixed to "Reasoning"
EasierlitApp.delete_message(thread_id, message_id)
EasierlitApp.list_threads(first=20, cursor=None, search=None, user_identifier=None)
EasierlitApp.get_thread(thread_id)
EasierlitApp.get_messages(thread_id) -> dict
EasierlitApp.new_thread(name=None, metadata=None, tags=None) -> str
EasierlitApp.update_thread(thread_id, name=None, metadata=None, tags=None)
EasierlitApp.delete_thread(thread_id)
EasierlitApp.reset_thread(thread_id)
EasierlitApp.close()
EasierlitAuthConfig(username, password, identifier=None, metadata=None)
EasierlitPersistenceConfig(
enabled=True,
sqlite_path=".chainlit/easierlit.db",
storage_provider=<auto LocalFileStorageClient>,
)
EasierlitDiscordConfig(enabled=True, bot_token=None)
For exact method contracts, use:
docs/api-reference.en.md
This includes parameter constraints, return semantics, exceptions, side effects, concurrency notes, and failure-mode fixes for each public method.
Auth and Persistence Defaults
- JWT secret: if
CHAINLIT_AUTH_SECRETis set but shorter than 32 bytes, Easierlit replaces it with a secure generated secret for the current run; if missing, it auto-manages.chainlit/jwt.secret - Auth cookie: keeps
CHAINLIT_AUTH_COOKIE_NAMEwhen set, otherwise uses scoped defaulteasierlit_access_token_<hash> - On shutdown, Easierlit restores the previous
CHAINLIT_AUTH_COOKIE_NAMEandCHAINLIT_AUTH_SECRET UVICORN_WS_PROTOCOLdefaults towebsockets-sansiowhen not set- Default auth is enabled when
auth=None - Auth credential order for
auth=None: EASIERLIT_AUTH_USERNAME+EASIERLIT_AUTH_PASSWORD(must be set together)- fallback to
admin/admin(warning log emitted) - Default persistence: SQLite at
.chainlit/easierlit.db(threads + text steps) - Default file/image storage:
LocalFileStorageClientis always enabled by default - Default local storage path:
<CHAINLIT_APP_ROOT or cwd>/public/easierlit LocalFileStorageClient(base_dir=...)supports~expansion- Relative
base_dirvalues resolve under<CHAINLIT_APP_ROOT or cwd>/public - Absolute
base_dirvalues outsidepublicare supported directly - Local files/images are served through
/easierlit/local/{object_key} - Local file/image URLs include both
CHAINLIT_PARENT_ROOT_PATHandCHAINLIT_ROOT_PATHprefixes - If SQLite schema is incompatible, Easierlit recreates DB with backup
- Sidebar default state is forced to
open - Discord bridge is disabled by default unless
discord=EasierlitDiscordConfig(...)is provided.
Thread History sidebar visibility follows Chainlit policy:
requireLogin=TruedataPersistence=True
Typical Easierlit setup:
- keep
auth=Noneandpersistence=Nonefor default enabled auth + persistence - optionally set
EASIERLIT_AUTH_USERNAME/EASIERLIT_AUTH_PASSWORDfor non-default credentials - pass
persistence=EasierlitPersistenceConfig(storage_provider=LocalFileStorageClient(...))to override local storage path/behavior - or pass explicit
auth=EasierlitAuthConfig(...)
Discord bot setup:
- Keep
discord=Noneto disable Discord integration. - Pass
discord=EasierlitDiscordConfig(...)to enable it. - Token precedence:
EasierlitDiscordConfig.bot_tokenfirst,DISCORD_BOT_TOKENfallback. - Discord replies are explicit: call
app.send_to_discord(...)when needed. - Discord-origin threads are upserted with runtime auth owner for stable Thread History visibility.
- Easierlit runs Discord through its own bridge (no runtime monkeypatching of Chainlit Discord handlers).
- During
serve(), Easierlit does not clearDISCORD_BOT_TOKEN; the env value remains unchanged. - If enabled and no non-empty token is available,
serve()raisesValueError.
Message and Thread Operations
Message APIs:
app.add_message(...)app.add_tool(...)app.add_thought(...)app.send_to_discord(...)app.is_discord_thread(...)app.update_message(...)app.update_tool(...)app.update_thought(...)app.delete_message(...)
Thread APIs:
app.list_threads(...)app.get_thread(thread_id)app.get_messages(thread_id)app.new_thread(...)app.update_thread(...)app.delete_thread(thread_id)app.reset_thread(thread_id)
Discord typing APIs:
app.discord_typing_open(thread_id)app.discord_typing_close(thread_id)
Behavior highlights:
app.add_message(...)returns generatedmessage_id.app.enqueue(...)mirrors input asuser_message(UI/data layer) and dispatches toon_message.app.add_tool(...)stores tool-call steps with tool name shown as step author/name.app.add_thought(...)is the same tool-call path with fixed tool nameReasoning.app.add_message(...)/app.add_tool(...)/app.add_thought(...)no longer auto-send to Discord.app.send_to_discord(...)sends only to Discord and returnsTrue/False.app.send_to_discord(..., elements=[...])can attach files/images to Discord.app.is_discord_thread(...)checks whether a thread is Discord-origin.app.discord_typing_open(...)starts Discord typing indicator for a mapped Discord thread and returnsTrue/False.app.discord_typing_close(...)stops Discord typing indicator for a mapped Discord thread and returnsTrue/False.- Discord typing indicator is explicit and is not auto-managed around
on_message. - Async awaitable execution is isolated by role:
run_funcawaitables run on a dedicated runner loop.on_messageawaitables run on a thread-aware runner pool sized asmin(max_message_workers, 8).- Same
thread_idis pinned to the sameon_messagerunner lane. - Runtime outgoing dispatcher uses thread-aware parallel lanes: same
thread_idorder is preserved, but global cross-thread outgoing order is not guaranteed. - CPU-bound Python handlers still share the GIL; use process-level offloading when true CPU isolation is required.
app.get_messages(...)returns thread metadata plus one orderedmessageslist.app.get_messages(...)includesuser_message/assistant_message/system_message/tooland excludes run-family steps.app.get_messages(...)mapsthread["elements"]into each message viaforIdaliases (forId/for_id/stepId/step_id).app.get_messages(...)addselements[*].has_sourceandelements[*].source(url/path/bytes/objectKey/chainlitKey) for image/file source tracing.app.new_thread(...)auto-generates a uniquethread_idand returns it.app.update_thread(...)updates only when thread already exists.- With auth enabled, both
app.new_thread(...)andapp.update_thread(...)auto-assign thread ownership. - SQLite SQLAlchemyDataLayer path auto normalizes thread
tags. - If no active websocket session exists, Easierlit applies internal HTTP-context fallback for data-layer message CRUD.
- Public
lock/unlockAPIs are intentionally not exposed.
Worker Failure Policy
Easierlit uses fail-fast behavior for worker crashes.
- If any
run_funcoron_messageraises, server shutdown is triggered. - UI gets a short summary when possible.
- Full traceback is kept in server logs.
Chainlit Message vs Tool-call
Chainlit distinguishes message and tool/run categories at step type level.
Message steps:
user_messageassistant_messagesystem_message
Tool/run family includes:
tool,run,llm,embedding,retrieval,rerank,undefined
Easierlit mapping:
app.add_message(...)->assistant_messageapp.add_tool(...)/app.update_tool(...)->toolapp.add_thought(...)/app.update_thought(...)->tool(name fixed toReasoning)app.send_to_discord(...)sends an explicit Discord reply without creating a step.app.delete_message(...)deletes bymessage_idregardless of message/tool/thought source.
Example Map
examples/minimal.py: basic echo botexamples/custom_auth.py: single-account authexamples/discord_bot.py: Discord bot configuration and token precedenceexamples/thread_crud.py: thread list/get/update/deleteexamples/thread_create_in_run_func.py: create thread fromrun_funcexamples/step_types.py: tool/thought step creation, update, delete example
Documentation Map
- Method-level API contracts (EN):
docs/api-reference.en.md - Method-level API contracts (KO):
docs/api-reference.ko.md - Full usage guide (EN):
docs/usage.en.md - Full usage guide (KO):
docs/usage.ko.md
Migration Note
API updates:
new_thread(thread_id=..., ...)->thread_id = new_thread(...)send(...)was removed.add_message(...)is now the canonical message API.- Added tool/thought APIs:
add_tool(...),add_thought(...),update_tool(...),update_thought(...). - Breaking behavior:
on_messageexceptions are now fail-fast (same asrun_func) and no longer emit an internal notice then continue.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file easierlit-1.2.4.tar.gz.
File metadata
- Download URL: easierlit-1.2.4.tar.gz
- Upload date:
- Size: 71.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2cbe93996ad90edc5f5ebae1b688a6cb12ed88a2d46f9094d00060b4706a301c
|
|
| MD5 |
dbe9c7816e37a004605ac05213189c3f
|
|
| BLAKE2b-256 |
2cd8fb2ea4d00513d0babb419553b7a0db3a2eda99df44d8e903cf0ff335f1cf
|
Provenance
The following attestation bundles were made for easierlit-1.2.4.tar.gz:
Publisher:
publish.yml on smturtle2/easierlit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
easierlit-1.2.4.tar.gz -
Subject digest:
2cbe93996ad90edc5f5ebae1b688a6cb12ed88a2d46f9094d00060b4706a301c - Sigstore transparency entry: 972888414
- Sigstore integration time:
-
Permalink:
smturtle2/easierlit@55e6cc5bb745625ebc66383c3ffe53a6760ed120 -
Branch / Tag:
refs/tags/v1.2.4 - Owner: https://github.com/smturtle2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@55e6cc5bb745625ebc66383c3ffe53a6760ed120 -
Trigger Event:
push
-
Statement type:
File details
Details for the file easierlit-1.2.4-py3-none-any.whl.
File metadata
- Download URL: easierlit-1.2.4-py3-none-any.whl
- Upload date:
- Size: 44.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9169efafa5ab30d25867dbe906d34f3941f83a6123111ed06a4a2df0f1ea8f72
|
|
| MD5 |
e66c53b9f657b7fc06d7a7762e4de862
|
|
| BLAKE2b-256 |
22b363be8ad6b7d50d1c60c731957f3c0336252855614b0033ece7e8db55891e
|
Provenance
The following attestation bundles were made for easierlit-1.2.4-py3-none-any.whl:
Publisher:
publish.yml on smturtle2/easierlit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
easierlit-1.2.4-py3-none-any.whl -
Subject digest:
9169efafa5ab30d25867dbe906d34f3941f83a6123111ed06a4a2df0f1ea8f72 - Sigstore transparency entry: 972888424
- Sigstore integration time:
-
Permalink:
smturtle2/easierlit@55e6cc5bb745625ebc66383c3ffe53a6760ed120 -
Branch / Tag:
refs/tags/v1.2.4 - Owner: https://github.com/smturtle2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@55e6cc5bb745625ebc66383c3ffe53a6760ed120 -
Trigger Event:
push
-
Statement type: