Chainlit: Membangun Chat UI Production-Ready untuk Aplikasi LLM

# Chainlit: Membangun Chat UI Production-Ready untuk Aplikasi LLM Membangun aplikasi LLM yang powerful saja tidak cukup. Pengguna membutuhkan **antarmuka chat yang intuitif dan profesional** untuk be...

By Ruby Abdullah · · tutorial
ChainlitChat UILLMWeb AppPython

Chainlit: Membangun Chat UI Production-Ready untuk Aplikasi LLM

Membangun aplikasi LLM yang powerful saja tidak cukup. Pengguna membutuhkan antarmuka chat yang intuitif dan profesional untuk berinteraksi dengan model AI. Namun, membangun UI chat dari nol membutuhkan waktu dan effort yang besar, mulai dari WebSocket, state management, hingga file handling.

Chainlit hadir sebagai framework Python yang memungkinkan Anda membangun chat UI production-ready dalam hitungan menit. Dengan Chainlit, Anda bisa fokus pada logika AI sambil mendapatkan antarmuka yang cantik, streaming response, file upload, dan banyak fitur lainnya secara otomatis.

Apa Itu Chainlit?

Chainlit adalah framework open-source Python untuk membangun antarmuka chat conversational AI. Fitur utamanya meliputi:

  • Chat UI Siap Produksi: Antarmuka modern dengan dukungan markdown, code highlighting, dan file preview
  • Streaming Response: Menampilkan respons LLM secara real-time token per token
  • Multi-Step Reasoning: Visualisasi proses berpikir AI dengan step-by-step display
  • File Upload & Download: Handling file bawaan untuk dokumen, gambar, dan lainnya
  • User Authentication: Sistem login terintegrasi dengan berbagai provider
  • Chat History: Penyimpanan dan recall percakapan sebelumnya
  • Integrasi LangChain/LlamaIndex: Dukungan native untuk framework AI populer
  • Custom Actions: Tombol interaktif dan aksi kustom dalam chat
  • Theming: Kustomisasi tampilan sesuai brand Anda

Instalasi

Instalasi Dasar

pip install chainlit

Instalasi dengan Dependensi Tambahan

# Dengan dukungan LangChain

pip install chainlit langchain langchain-openai

Dengan dukungan LlamaIndex

pip install chainlit llama-index

Verifikasi Instalasi

chainlit --version

Menjalankan Aplikasi Pertama

Buat file app.py:

import chainlit as cl

@cl.onmessage

async def main(message: cl.Message):

# Kirim respons sederhana

await cl.Message(

content=f"Anda mengirim: {message.content}"

).send()

Jalankan dengan:

chainlit run app.py

Buka browser di http://localhost:8000 dan Anda akan melihat antarmuka chat yang profesional.

Konsep Dasar: Decorator dan Message

@cl.onmessage - Handler Pesan Utama

Decorator @cl.onmessage adalah entry point utama. Fungsi ini dipanggil setiap kali pengguna mengirim pesan.

import chainlit as cl

@cl.onmessage

async def handlemessage(message: cl.Message):

# Akses konten pesan

userinput = message.content

# Akses file yang diunggah (jika ada)

files = message.elements

# Proses dan kirim respons

response = f"Pesan diterima: {userinput}"

await cl.Message(content=response).send()

@cl.onchatstart - Inisialisasi Chat

Decorator ini dipanggil saat sesi chat baru dimulai. Gunakan untuk menyiapkan konteks, model, atau menyapa pengguna.

import chainlit as cl

@cl.onchatstart

async def start():

# Simpan state di session

cl.usersession.set("history", [])

# Kirim pesan sambutan

await cl.Message(

content="Halo! Saya adalah asisten AI. Ada yang bisa saya bantu?"

).send()

@cl.onmessage

async def main(message: cl.Message):

# Ambil history dari session

history = cl.usersession.get("history")

history.append({"role": "user", "content": message.content})

# Proses pesan...

response = "Respons dari AI"

history.append({"role": "assistant", "content": response})

cl.usersession.set("history", history)

await cl.Message(content=response).send()

Tipe Pesan: Teks, Gambar, dan File

Pesan Teks dengan Markdown

@cl.onmessage

async def main(message: cl.Message):

# Chainlit mendukung markdown penuh

response = """

Hasil Analisis

Berikut adalah hasil analisis data Anda:

| Metrik | Nilai |

|--------|-------|

| Akurasi | 95.2% |

| Presisi | 93.8% |

| Recall | 96.1% |

Kesimpulan: Model menunjukkan performa yang sangat baik.

"""

await cl.Message(content=response).send()

Mengirim Gambar

@cl.onmessage

async def main(message: cl.Message):

# Kirim gambar sebagai element

image = cl.Image(

path="./chart.png",

name="analysischart",

display="inline"

)

await cl.Message(

content="Berikut visualisasi hasil analisis:",

elements=[image]

).send()

Mengirim File untuk Diunduh

@cl.onmessage

async def main(message: cl.Message):

# Kirim file sebagai attachment

file = cl.File(

path="./report.pdf",

name="laporananalisis.pdf",

display="inline"

)

await cl.Message(

content="Laporan telah selesai dibuat. Silakan unduh:",

elements=[file]

).send()

Streaming Response

Streaming memungkinkan respons ditampilkan secara real-time, memberikan pengalaman pengguna yang lebih baik.

Streaming Dasar

import chainlit as cl

from openai import AsyncOpenAI

client = AsyncOpenAI()

@cl.onmessage

async def main(message: cl.Message):

msg = cl.Message(content="")

# Streaming dari OpenAI

stream = await client.chat.completions.create(

model="gpt-4",

messages=[

{"role": "user", "content": message.content}

],

stream=True

)

async for chunk in stream:

if chunk.choices[0].delta.content:

await msg.streamtoken(chunk.choices[0].delta.content)

# Finalisasi pesan

await msg.send()

Streaming dengan Token Tracking

@cl.onmessage

async def main(message: cl.Message):

msg = cl.Message(content="")

totaltokens = 0

stream = await client.chat.completions.create(

model="gpt-4",

messages=[{"role": "user", "content": message.content}],

stream=True,

streamoptions={"includeusage": True}

)

async for chunk in stream:

if chunk.choices and chunk.choices[0].delta.content:

await msg.streamtoken(chunk.choices[0].delta.content)

if chunk.usage:

totaltokens = chunk.usage.totaltokens

await msg.send()

# Tampilkan info token

await cl.Message(

content=f"Total token yang digunakan: {totaltokens}"

).send()

Multi-Step Reasoning Display

Chainlit dapat menampilkan proses berpikir AI secara bertahap menggunakan Steps.

import chainlit as cl

@cl.onmessage

async def main(message: cl.Message):

# Step 1: Analisis pertanyaan

async with cl.Step(name="Menganalisis Pertanyaan") as step:

step.input = message.content

# Simulasi analisis

analysis = "Pertanyaan tentang machine learning classification"

step.output = analysis

# Step 2: Pencarian dokumen

async with cl.Step(name="Mencari Dokumen Relevan") as step:

step.input = analysis

# Simulasi pencarian

docs = "Ditemukan 5 dokumen relevan"

step.output = docs

# Step 3: Generasi jawaban

async with cl.Step(name="Menghasilkan Jawaban") as step:

step.input = f"Query: {analysis}\nDokumen: {docs}"

answer = "Berdasarkan dokumen yang ditemukan..."

step.output = answer

# Kirim jawaban final

await cl.Message(content=answer).send()

File Upload Handling

Meminta File dari Pengguna

import chainlit as cl

@cl.onchatstart

async def start():

files = await cl.AskFileMessage(

content="Silakan unggah file CSV untuk dianalisis:",

accept=["text/csv"],

maxsizemb=10,

maxfiles=1

).send()

if files:

file = files[0]

# Baca konten file

import pandas as pd

df = pd.readcsv(file.path)

await cl.Message(

content=f"File {file.name} berhasil diunggah!\n\n"

f"- Jumlah baris: {len(df)}\n"

f"- Jumlah kolom: {len(df.columns)}\n"

f"- Kolom: {', '.join(df.columns.tolist())}"

).send()

cl.usersession.set("dataframe", df)

Menangani Multiple File Types

@cl.onchatstart

async def start():

files = await cl.AskFileMessage(

content="Unggah dokumen Anda (PDF, TXT, atau DOCX):",

accept={

"application/pdf": [".pdf"],

"text/plain": [".txt"],

"application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"]

},

maxsizemb=20,

maxfiles=5

).send()

if files:

fileinfo = []

for f in files:

fileinfo.append(f"- {f.name} ({f.type}, {f.size} bytes)")

await cl.Message(

content=f"Berhasil mengunggah {len(files)} file:\n" +

"\n".join(fileinfo)

).send()

User Authentication

Password Authentication

Buat file .chainlit/config.toml:

[project]

name = "My AI Assistant"

enabletelemetry = false

[UI]

name = "AI Assistant"

Implementasi autentikasi:

import chainlit as cl

@cl.passwordauthcallback

def authcallback(username: str, password: str):

# Validasi kredensial (gunakan database di produksi)

if username == "admin" and password == "secret123":

return cl.User(

identifier="admin",

metadata={"role": "admin", "name": "Administrator"}

)

return None

@cl.onchatstart

async def start():

user = cl.usersession.get("user")

await cl.Message(

content=f"Selamat datang, {user.metadata['name']}!"

).send()

Header Authentication (untuk Reverse Proxy)

@cl.headerauthcallback

def headerauth(headers: dict):

# Autentikasi via header (misalnya dari reverse proxy)

if headers.get("X-User-Email"):

return cl.User(

identifier=headers["X-User-Email"],

metadata={"provider": "header"}

)

return None

Chat History dan Persistence

Menyimpan Chat History

import chainlit as cl

import json

from pathlib import Path

HISTORYDIR = Path("chathistory")

HISTORYDIR.mkdir(existok=True)

@cl.onchatstart

async def start():

cl.usersession.set("history", [])

@cl.onmessage

async def main(message: cl.Message):

history = cl.usersession.get("history")

history.append({

"role": "user",

"content": message.content

})

# Proses dengan LLM

response = await generateresponse(history)

history.append({

"role": "assistant",

"content": response

})

cl.usersession.set("history", history)

await cl.Message(content=response).send()

@cl.onchatend

async def end():

history = cl.usersession.get("history")

if history:

# Simpan ke file

sessionid = cl.usersession.get("id")

filepath = HISTORYDIR / f"{sessionid}.json"

with open(filepath, "w") as f:

json.dump(history, f, indent=2)

Menggunakan Chainlit Data Layer

Chainlit menyediakan data layer abstraction untuk menyimpan percakapan secara persisten.

import chainlit.data as cldata

from chainlit.data.sql import SQLAlchemyDataLayer

Konfigurasi data layer dengan PostgreSQL

cldata.init(

SQLAlchemyDataLayer(

conninfo="postgresql://user:pass@localhost/chainlitdb"

)

)

Integrasi dengan LangChain

import chainlit as cl

from langchainopenai import ChatOpenAI

from langchain.schema import HumanMessage, AIMessage, SystemMessage

from langchain.schema.runnable.config import RunnableConfig

@cl.onchatstart

async def start():

model = ChatOpenAI(

model="gpt-4",

temperature=0.7,

streaming=True

)

cl.usersession.set("model", model)

cl.usersession.set("history", [

SystemMessage(content="Kamu adalah asisten AI yang membantu.")

])

@cl.onmessage

async def main(message: cl.Message):

model = cl.usersession.get("model")

history = cl.usersession.get("history")

history.append(HumanMessage(content=message.content))

msg = cl.Message(content="")

async for chunk in model.astream(

history,

config=RunnableConfig(callbacks=[cl.LangchainCallbackHandler()])

):

await msg.streamtoken(chunk.content)

history.append(AIMessage(content=msg.content))

cl.usersession.set("history", history)

await msg.send()

Integrasi dengan LlamaIndex

import chainlit as cl

from llamaindex.core import (

VectorStoreIndex,

SimpleDirectoryReader,

Settings

)

from llamaindex.llms.openai import OpenAI

@cl.onchatstart

async def start():

Settings.llm = OpenAI(model="gpt-4", temperature=0.1)

# Muat dan indeks dokumen

await cl.Message(content="Memuat dokumen...").send()

documents = SimpleDirectoryReader("./data").loaddata()

index = VectorStoreIndex.fromdocuments(documents)

queryengine = index.asqueryengine(streaming=True)

cl.usersession.set("queryengine", queryengine)

await cl.Message(

content="Dokumen berhasil dimuat! Silakan ajukan pertanyaan."

).send()

@cl.onmessage

async def main(message: cl.Message):

queryengine = cl.usersession.get("queryengine")

msg = cl.Message(content="")

response = await cl.makeasync(queryengine.query)(message.content)

# Tampilkan sumber

sources = []

for node in response.sourcenodes:

sources.append(f"- {node.metadata.get('filename', 'Unknown')} "

f"(score: {node.score:.3f})")

sourcetext = "\n\nSumber:\n" + "\n".join(sources)

await cl.Message(

content=str(response) + sourcetext

).send()

Custom Actions dan Buttons

import chainlit as cl

@cl.onchatstart

async def start():

# Kirim pesan dengan tombol aksi

actions = [

cl.Action(

name="summarize",

payload={"action": "summarize"},

label="Ringkas Teks",

description="Meringkas teks yang diberikan"

),

cl.Action(

name="translate",

payload={"action": "translate"},

label="Terjemahkan",

description="Terjemahkan ke bahasa lain"

),

cl.Action(

name="analyze",

payload={"action": "analyze"},

label="Analisis Sentimen",

description="Analisis sentimen teks"

)

]

await cl.Message(

content="Pilih aksi yang ingin dilakukan:",

actions=actions

).send()

@cl.actioncallback("summarize")

async def onsummarize(action: cl.Action):

await cl.Message(content="Silakan kirim teks yang ingin diringkas.").send()

cl.usersession.set("mode", "summarize")

@cl.actioncallback("translate")

async def ontranslate(action: cl.Action):

await cl.Message(content="Silakan kirim teks yang ingin diterjemahkan.").send()

cl.usersession.set("mode", "translate")

@cl.actioncallback("analyze")

async def onanalyze(action: cl.Action):

await cl.Message(content="Silakan kirim teks untuk analisis sentimen.").send()

cl.usersession.set("mode", "analyze")

Theming dan Kustomisasi

Konfigurasi Theme

Buat file .chainlit/config.toml:

[project]

name = "AI Assistant Pro"

enabletelemetry = false

[UI]

name = "AI Assistant Pro"

description = "Asisten AI cerdas untuk kebutuhan Anda"

defaulttheme = "dark"

[UI.theme]

fontfamily = "Inter, sans-serif"

[UI.theme.light]

background = "#FFFFFF"

paper = "#F5F5F5"

[UI.theme.dark]

background = "#1A1A2E"

paper = "#16213E"

Custom Logo dan Favicon

Letakkan file di folder public/:

public/

logodark.png

logolight.png

favicon.png

Custom CSS

Buat file public/stylesheet.css:

/ Kustomisasi tampilan chat /

.message-content {

font-size: 15px;

line-height: 1.6;

}

/ Kustomisasi code block /

pre code {

font-size: 13px;

border-radius: 8px;

}

Deployment

Menggunakan Docker

Buat Dockerfile:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "8000"]

Buat docker-compose.yml:

version: '3.8'

services:

chainlit-app:

build: .

ports:

  • "8000:8000"
environment:

  • OPENAIAPIKEY=${OPENAIAPIKEY}
volumes:

  • ./data:/app/data
  • ./chathistory:/app/chathistory
restart: unless-stopped

Deploy ke Cloud

# Build dan jalankan

docker build -t chainlit-app .

docker run -d -p 8000:8000 \

-e OPENAIAPIKEY=$OPENAIAPIKEY \

chainlit-app

Contoh Praktis: RAG Chatbot dengan File Upload dan Source Citations

Berikut adalah contoh lengkap membangun RAG chatbot yang mendukung file upload dan menampilkan sumber kutipan.

import chainlit as cl

from langchainopenai import ChatOpenAI, OpenAIEmbeddings

from langchain.textsplitter import RecursiveCharacterTextSplitter

from langchaincommunity.vectorstores import FAISS

from langchain.chains import ConversationalRetrievalChain

from langchain.memory import ConversationBufferWindowMemory

from langchaincommunity.documentloaders import (

PyPDFLoader,

TextLoader,

CSVLoader

)

import tempfile

import os

Konfigurasi

CHUNKSIZE = 1000

CHUNKOVERLAP = 200

def loaddocument(filepath: str, filetype: str):

"""Muat dokumen berdasarkan tipe file."""

if filetype == "application/pdf":

loader = PyPDFLoader(filepath)

elif filetype == "text/csv":

loader = CSVLoader(filepath)

else:

loader = TextLoader(filepath)

return loader.load()

@cl.onchatstart

async def start():

# Minta pengguna mengunggah dokumen

files = await cl.AskFileMessage(

content="Selamat datang di RAG Chatbot!\n\n"

"Silakan unggah dokumen yang ingin Anda tanyakan "

"(PDF, TXT, atau CSV):",

accept={

"application/pdf": [".pdf"],

"text/plain": [".txt"],

"text/csv": [".csv"]

},

maxsizemb=20,

maxfiles=5

).send()

if not files:

return

# Proses dokumen

processingmsg = cl.Message(content="Memproses dokumen Anda...")

await processingmsg.send()

alldocs = []

for file in files:

docs = loaddocument(file.path, file.type)

for doc in docs:

doc.metadata["source"] = file.name

alldocs.extend(docs)

# Split dokumen

async with cl.Step(name="Memecah Dokumen") as step:

textsplitter = RecursiveCharacterTextSplitter(

chunksize=CHUNKSIZE,

chunkoverlap=CHUNKOVERLAP

)

splits = textsplitter.splitdocuments(alldocs)

step.output = f"Dokumen dipecah menjadi {len(splits)} bagian"

# Buat vector store

async with cl.Step(name="Membuat Vector Index") as step:

embeddings = OpenAIEmbeddings()

vectorstore = FAISS.fromdocuments(splits, embeddings)

step.output = "Vector index berhasil dibuat"

# Setup retrieval chain

memory = ConversationBufferWindowMemory(

memorykey="chathistory",

returnmessages=True,

outputkey="answer",

k=5

)

llm = ChatOpenAI(

model="gpt-4",

temperature=0.3,

streaming=True

)

chain = ConversationalRetrievalChain.fromllm(

llm=llm,

retriever=vectorstore.asretriever(searchkwargs={"k": 4}),

memory=memory,

returnsourcedocuments=True,

verbose=False

)

# Simpan di session

cl.usersession.set("chain", chain)

cl.usersession.set("vectorstore", vectorstore)

# Notifikasi sukses

filenames = ", ".join([f.name for f in files])

await cl.Message(

content=f"Dokumen berhasil diproses: {filenames}\n\n"

f"Total {len(splits)} bagian teks telah diindeks. "

f"Silakan ajukan pertanyaan tentang dokumen Anda!"

).send()

@cl.onmessage

async def main(message: cl.Message):

chain = cl.usersession.get("chain")

if not chain:

await cl.Message(

content="Silakan unggah dokumen terlebih dahulu."

).send()

return

# Jalankan query dengan steps

async with cl.Step(name="Mencari Konteks Relevan") as step:

step.input = message.content

result = await cl.makeasync(chain.invoke)(

{"question": message.content}

)

step.output = f"Ditemukan {len(result['sourcedocuments'])} sumber"

# Format sumber kutipan

sources = []

seen = set()

for doc in result["sourcedocuments"]:

sourcename = doc.metadata.get("source", "Unknown")

page = doc.metadata.get("page", "N/A")

key = f"{sourcename}-{page}"

if key not in seen:

seen.add(key)

snippet = doc.pagecontent[:100].replace("\n", " ")

sources.append(

f"- {sourcename} (hal. {page}): \"{snippet}...\""

)

# Kirim jawaban dengan sumber

answer = result["answer"]

if sources:

answer += "\n\nSumber Kutipan:\n" + "\n".join(sources)

await cl.Message(content=answer).send()

Menjalankan RAG Chatbot

# Install dependensi

pip install chainlit langchain langchain-openai langchain-community \

faiss-cpu pypdf

Set API key

export OPENAIAPIKEY="sk-your-api-key"

Jalankan

chainlit run app.py

Tips dan Best Practices

  • Gunakan Session untuk State: Selalu simpan state di cl.usersession karena setiap pengguna memiliki session terpisah
  • Streaming untuk UX: Selalu gunakan streaming response untuk pengalaman pengguna yang lebih baik
  • Error Handling: Tangani error dengan pesan yang jelas agar pengguna tidak bingung
  • Steps untuk Transparansi: Gunakan cl.Step untuk menampilkan proses di balik layar
  • Rate Limiting: Implementasikan rate limiting untuk mencegah penyalahgunaan di produksi
  • Kesimpulan

    Chainlit adalah pilihan yang sangat baik untuk membangun antarmuka chat AI production-ready. Dengan API yang sederhana namun powerful, Anda bisa fokus pada logika AI tanpa harus menghabiskan waktu membangun UI dari nol. Integrasi native dengan LangChain dan LlamaIndex membuatnya semakin mudah untuk membangun aplikasi RAG yang lengkap dengan fitur enterprise seperti autentikasi, chat history, dan deployment yang mudah.

    Artikel Terkait

    Tutorial MergeKit: Menggabungkan Model LLM untuk Membuat Model AI Kustom

    Tutorial MergeKit: Menggabungkan Model LLM untuk Membuat Model AI Kustom Model bahasa besar (LLM) open-source terus berk...

    Tutorial Semantic Router: Routing Cerdas untuk Aplikasi LLM

    Tutorial Semantic Router: Routing Cerdas untuk Aplikasi LLM Semantic Router adalah library Python yang memungkinkan Anda...

    Tutorial Lengkap Unstructured: Preprocessing Dokumen untuk RAG dan Aplikasi LLM

    Tutorial Lengkap Unstructured: Preprocessing Dokumen untuk RAG dan Aplikasi LLM Dalam membangun aplikasi Retrieval-Augme...

    Tutorial Lengkap Llama Stack: Framework Resmi Meta untuk Membangun Aplikasi AI dengan Model Llama

    Tutorial Lengkap Llama Stack: Framework Resmi Meta untuk Membangun Aplikasi AI dengan Model Llama Llama Stack adalah fra...