Cursus
Chainlit is een krachtig open-source Python-framework waarmee je met minimale code en zonder front-end gedoe interactieve interfaces bouwt voor je LLM-aangedreven applicaties.
In deze tutorial neem ik je mee door:
- Chainlit installeren en instellen.
- De kernconcepten begrijpen, zoals acties, chat-levenscyclus en configuraties.
- Twee voorbeeldapps bouwen:
- Een statische “Verras me”-chatbot met knoppen
- Een “Verras me”-bot aangedreven door Ollama
Wat is Chainlit?
Chainlit helpt je frontends te maken voor AI-chatbots, tools en LLM-workflows. Het haalt de front-endcomplexiteit weg en laat je focussen op Python-logica, met ondersteuning voor het toevoegen van knoppen, sliders, bestandsuploads of zelfs het koppelen aan tools via het model context protocol (MCP).
Chainlit is ideaal voor:
- Prototypen van LLM-gebaseerde apps
- Interne tools maken
- Educatieve demo's bouwen
- Je modellen verbinden met externe tools of API's
Chainlit-componenten
Elke Chainlit-app is opgebouwd uit een paar essentiële features:
- Hooks voor de chat-levenscyclus: Hiermee bepaal je wat er gebeurt in verschillende fases van een chat. Bijvoorbeeld:
@cl.on_chat_startdraait wanneer een chat begint.@cl.on_messagedraait wanneer de gebruiker een bericht stuurt.@cl.on_chat_enddraait wanneer de chat eindigt.- UI-acties: Met Chainlit kun je knoppen toevoegen met
cl.Actionen ze afhandelen met@cl.action_callback. Ideaal voor schone, interactieve UI's zonder tekstinvoer van de gebruiker. - Berichtstreaming: Met LLM's die tokenstreaming ondersteunen, kun je reacties realtime streamen met
stream=True, waardoor je app dynamischer en responsiever aanvoelt. - Configuratie met
config.toml: Met dit bestand kun je features als chatpersistentie, bestandsuploads, theming en usability-aanpassingen inschakelen—zonder je Python-code te wijzigen.
Ik leg elk concept uit met een hands-on codevoorbeeld, zodat je precies ziet hoe het werkt en hoe je het in je Chainlit-apps integreert. Laten we beginnen met de eerste stap.
Vereisten
Zorg dat je Python 3.8+ hebt geïnstalleerd en installeer vervolgens Chainlit met pip. We gebruiken ook Langchain voor de Ollama-demo, dus installeer dat ook. Voer de volgende opdracht uit in de terminal:
pip install chainlit langchain langchain-community
Chainlit-basics begrijpen aan de hand van voorbeelden
In dit gedeelte behandelen we de kernbouwstenen van Chainlit, die de basis vormen van elk project.
Chat-levenscyclushooks
Telkens wanneer een gebruiker verbinding maakt met je Chainlit-app, wordt er een chatsessie aangemaakt. Je kunt in verschillende stadia van die sessie inhaken met de decorators die Chainlit biedt. Deze omvatten:
Bouwen met @cl.on_chat_start
Deze hook draait wanneer een nieuwe chatsessie begint. Je kunt hem gebruiken om de gebruiker te begroeten, een welkomstbericht te tonen of de sessiestatus te initialiseren.
import chainlit as cl
@cl.on_chat_start
def on_chat_start():
print("A new chat session has started!")

Bouwen met @cl.on_message
Deze message-hook draait wanneer de gebruiker een nieuw bericht stuurt. We gebruiken hem om invoer van de gebruiker te verwerken, een LLM aan te roepen of een antwoord te geven.
import chainlit as cl
@cl.on_message
async def on_message(msg: cl.Message):
print("The user sent:", msg.content)
await cl.Message(content=f"You said: {msg.content}").send()

Bouwen met @cl.on_stop
De on_stop-hook draait wanneer de gebruiker op de stopknop (⏹) klikt tijdens een lopende taak. Dit is bedoeld om langlopende operaties te annuleren of onderbroken sessies op te ruimen.
import chainlit as cl
import asyncio
@cl.on_chat_start
async def start():
await cl.Message("Type anything and I'll pretend to work on it.").send()
@cl.on_message
async def on_message(msg: cl.Message):
await cl.Message("Working on it... you can press Stop").send()
try:
# Simulate a long task
await asyncio.sleep(10)
await cl.Message("Task complete!").send()
except asyncio.CancelledError:
print("Task was interrupted!")
# This is optional, just logs in the server
raise
@cl.on_stop
async def on_stop():
print("The user clicked Stop!")
Wanneer de gebruiker een bericht stuurt, simuleert Chainlit een taak met asyncio.sleep(10). Als de gebruiker op de stopknop (⏹) klikt, wordt de taak geannuleerd en wordt @cl.on_stop geactiveerd om de onderbreking te loggen.

Bouwen met @cl.on_chat_end
Deze hook wordt geactiveerd wanneer de sessie eindigt—wanneer de gebruiker ververst, het tabblad sluit of een nieuwe sessie start. Dit wordt meestal gebruikt om disconnecties te loggen of de status op te slaan.
import chainlit as cl
@cl.on_chat_start
async def on_chat_start():
await cl.Message("Welcome! Feel free to leave anytime").send()
@cl.on_chat_end
async def on_chat_end():
print("The user disconnected!")
Zodra localhost is geopend, kun je ermee werken. Wanneer je het localhost-tabblad of -venster sluit, toont de terminal het volgende:

UI-acties (knoppen)
Met Chainlit kun je interactieve knoppen rechtstreeks in je chatbotinterface toevoegen. Elke knop wordt gedefinieerd als een actie en gekoppeld aan een Python-callbackfunctie. Je verstuurt knoppen als onderdeel van een Message via het actions-argument:
import chainlit as cl
@cl.on_chat_start
async def start():
actions = [
cl.Action(
name="hello",
label="👋 Say Hello",
icon="smile",
payload={"value": "hi"}
)
]
await cl.Message("Click a button!", actions=actions).send()
Zo verwerk je een knopklik:
@cl.action_callback("hello")
async def on_hello(action: cl.Action):
await cl.Message("Hello there! 👋").send()
De decorator @cl.action_callback("hello") vertelt Chainlit te luisteren naar kliks op een knop met de naam "hello". Bij een klik stuurt hij een vriendelijk bericht terug naar de gebruiker in de chatinterface.

Tip: Je kunt de payload aanpassen met willekeurige data die je terug naar de server wilt sturen.
Berichtstreaming
Chainlit ondersteunt realtime streaming van LLM-antwoorden. Dat betekent dat je content stapsgewijs naar de gebruiker kunt sturen terwijl deze wordt gegenereerd.
@cl.on_message
async def on_message(message: cl.Message):
await cl.Message(content="Thinking...").send()
async for chunk in llm.astream(message.content):
await cl.Message(content=chunk, author="LLM", stream=True).send()
Laten we dit opsplitsen:
- Eerst verschijnt direct het bericht "Thinking...", zodat de gebruiker weet dat de app bezig is.
- Vervolgens wordt het bericht van de gebruiker naar een streaming-compatibele LLM gestuurd.
- Terwijl het model output genereert, wordt elk stukje (chunk) realtime teruggestreamd naar de UI.
- De parameter
stream=Truezorgt ervoor dat elk chunk incrementeel verschijnt in plaats van te wachten op het volledige antwoord.
Opmerking: Dit werkt het best met modellen die streaming ondersteunen.
Chainlit-configuratie (config.toml)
Om krachtige features zoals chatpersistentie, bestandsuploads, theming en meer te ontgrendelen, kun je je Chainlit-app aanpassen met het bestand config.toml. Dit bestand staat in de hoofdmap van je project (in de map .chainlit) en laat je runtimegedrag aanpassen zonder code te wijzigen.
Persistentie
Met deze parameter kan Chainlit chathistorie en sessiestatus bewaren. Het activeert de hook @cl.on_chat_resume en is ideaal voor apps waar gebruikers later kunnen terugkeren.
[persistence]
enabled = true
Bestanden uploaden
Met deze instelling kunnen gebruikers bestanden uploaden in de chatinterface. Je kunt toegestane bestandstypen en -groottes beperken voor veiligheid en prestaties.
[features.spontaneous_file_upload]
enabled = true
accept = ["*/*"]
max_files = 5
max_size_mb = 500
Hier zijn een paar bestandstypen die door Chainlit worden geaccepteerd.
# 1. For specific file types:
# accept = ["image/jpeg", "image/png", "application/pdf"]
# 2. For all files of a certain type:
# accept = ["image/*", "audio/*", "video/*"]
# 3. For specific file extensions:
# accept = { "application/octet-stream" = [".xyz", ".pdb"] }
Hiermee kun je uploads afstemmen op veiligheid, prestaties of domeinspecifieke use-cases.
UI-aanpassingen
Deze aanpassing wijzigt de assistentnaam onder de UI-parameter en schakelt de Chain of Thought (CoT) in de weergavemodus in, wat handig is voor stapsgewijze redenering of debugging.
[UI]
name = "Assistant"
cot = "full"
Usability-tweaks
Deze verbeteren de gebruikerservaring door nieuwe berichten automatisch in beeld te laten scrollen en berichten te kunnen bewerken.
[features]
user_message_autoscroll = true
edit_message = true
Met de bovenstaande usability-tweaks kan de gebruiker autoscrollen en berichtbewerking in de UI inschakelen.
Project: Verras me-bot (zonder LLM)
Nu we de basiscomponenten van Chainlit kennen, bouwen we een eenvoudige UI-gebaseerde app die knoppen gebruikt om vooraf gedefinieerde fun facts of motiverende berichten voor developers te tonen.
import chainlit as cl
import random
FUN_FACTS = [
"💡 Did you know? Chainlit supports file uploads and custom themes!",
"💡 You can add buttons, sliders, and images directly in your chatbot UI!",
"💡 Chainlit supports real-time tool execution with LangChain and LLMs!",
"💡 You can customize the look of your chatbot with just a CSS file!",
"💡 Chainlit lets you connect to tools using Model Context Protocol (MCP)!"
]
SUPRISES = [
"🎉 Surprise! You're doing great!",
"🚀 Keep it up, you're making awesome progress!",
"🌟 Fun fact: Someone out there just smiled because of you. Why not make it two?",
"👏 Bravo! You just unlocked +10 imaginary developer XP!",
"💪 Remember: Even bugs fear your debugging skills!"
]
@cl.on_chat_start
async def start():
actions = [
cl.Action(
name="surprise_button",
label="🎁 Surprise Me",
icon="gift",
payload={"value": "surprise"}
),
cl.Action(
name="fact_button",
label="💡 Did You Know?",
icon="lightbulb",
payload={"value": "fact"}
)
]
await cl.Message(content="Choose an action:", actions=actions).send()
@cl.action_callback("surprise_button")
async def on_surprise(action: cl.Action):
suprise = random.choice(SUPRISES)
await cl.Message(content=suprise).send()
@cl.action_callback("fact_button")
async def on_fact(action: cl.Action):
fact = random.choice(FUN_FACTS)
await cl.Message(content=fact).send()
De bovenstaande code definieert twee lijsten: “FUN_FACTS” voor interessante Chainlit-tips en “SUPRISES” voor motiverende berichten.
- Wanneer een nieuwe chat start, worden twee knoppen getoond: "Surprise Me" en "Did You Know?"—met
cl.Action. ' - Klik op "Surprise Me" triggert @cl.action_callback("surprise_button")
, dat een willekeurig verrassingsbericht stuurt. - Klik op "Did You Know?" triggert @cl.action_callback("fact_button")`, dat een willekeurige fun fact stuurt.
Dit levert een eenvoudige, interactieve chatbot op met knoppen, zonder LLM's, perfect om te leren hoe Chainlit-acties en callbacks werken.
Om deze applicatie te draaien, voer je simpelweg de volgende opdracht uit in de terminal:
chainlit run main.py
Je ziet interactieve knoppen in de UI die feiten of leuke berichten triggeren!

Project: Verras me-bot aangedreven door Ollama
Laten we dit proces nu automatiseren en de verras- en feitberichten genereren met een lokale LLM via Ollama.
import chainlit as cl
from langchain_community.llms import Ollama
import random
llm = Ollama(model="mistral", temperature=0.7) # Use any lightweight local model
# Reusable action buttons
def get_action_buttons():
return [
cl.Action(
name="surprise_button",
label="🎁 Surprise Me",
icon="gift",
payload={"value": "surprise"}
),
cl.Action(
name="fact_button",
label="💡 Did You Know?",
icon="lightbulb",
payload={"value": "fact"}
)
]
@cl.on_chat_start
async def start():
await cl.Message(content="Choose an action below to see something fun:").send()
await cl.Message(content="", actions=get_action_buttons()).send()
@cl.action_callback("surprise_button")
async def on_surprise(action: cl.Action):
prompt = "Give a short, uplifting surprise message to a developer. Make it fun."
try:
surprise = llm.invoke(prompt).strip()
except Exception:
surprise = "🎉 Surprise! You're doing great!"
await cl.Message(content=surprise).send()
await cl.Message(content="", actions=get_action_buttons()).send()
@cl.action_callback("fact_button")
async def on_fact(action: cl.Action):
prompt = "Give a fun and helpful fact about LLMs or Chainlit."
try:
fact = llm.invoke(prompt).strip()
except Exception:
fact = "💡 Did you know? You can add sliders and buttons to Chainlit with just a few lines of code!"
await cl.Message(content=fact).send()
await cl.Message(content="", actions=get_action_buttons()).send()
Deze Chainlit-app integreert een lokale LLM (via Ollama met het Mistral-model) om dynamisch reacties te genereren op basis van gebruikersinteracties.
- Er worden twee
cl.Action-knoppen gedefinieerd—"Surprise Me" en "Did You Know?"—met een herbruikbare functieget_action_buttons(). - Bij initialisatie van de chat (
@cl.on_chat_start) wordt een bericht met deze interactieve knoppen verstuurd. - Wanneer de gebruiker op de knop "Surprise Me" klikt, stuurt de
@cl.action_callback-handler een prompt naar de LLM met het verzoek om een korte, opbeurende developersboodschap. Het antwoord wordt opgeschoond met de functie.strip()en teruggestuurd naar de chat. - Op vergelijkbare wijze roept een klik op "Did You Know?" de LLM aan met een prompt voor een informatief feit over Chainlit of LLM's.
- Als de LLM faalt, worden fallback-berichten geretourneerd.
- Na elke interactie worden de knoppen opnieuw verstuurd om de gesprekslus te behouden.
Dit laat zien hoe je de Chainlit-UI combineert met LLM-gebaseerde contentgeneratie, als modulaire en uitbreidbare basis voor lokale AI-chatapps.
Voer de volgende opdracht uit in de terminal:
ollama run mistral
chainlit run main.py
Nu heb je een live, lokale AI-assistent die automatische berichten genereert.

Conclusie
Chainlit heeft veel potentie voor het prototypen van chatgebaseerde tools, het bouwen van assistenten en het integreren van realtime LLM-outputs—allemaal zonder JavaScript aan te raken. Voor gevorderde use-cases, bekijk de Chainlit Cookbook.
Wil je je kennis uitbreiden met nieuwe AI-tools? Bekijk dan deze blogs:
Ik ben een Google Developers Expert in ML (Gen AI), een Kaggle 3x Expert en een Women Techmakers Ambassador met meer dan 3 jaar ervaring in tech. In 2020 heb ik een healthtech-startup mee opgericht en ik volg een master computer science aan Georgia Tech, met als specialisatie machine learning.

