如果你已经用过一些 LLM SDK(其实我只用过Langchain),你会发现一个共性问题:模型调用很快能写出来,但“像样的聊天界面 + 会话管理 + 上线部署”这三件事会很快把代码变复杂。
Chainlit就是专门解决这个问题的:用很少的 Python 代码把对话应用搭起来,并且保留足够的扩展能力。
好吧, 你可能说,我可以让AI生成前端呀, 但是吧, 一是它不一定符合你的需求, 二是它需要你去维护(维护很费成本的哦),三是完善的前端很费token呢。
当然啦,也可以直接使用langchain自带的部署功能,不过还需要注册个账户,还不知道后面会不会额外收费,总的来说感觉差了一点什么,可能这是大多数流行应用不用langchain的原因?
其实langsmith的功能是真的棒,可是我就是不想注册个账户而已.
其实除了选择Chainlit还有很多其他的方式接入其他前端方案,比如Streamlit或者将Langchain包装一下,使用fastapi提供兼容OpenAI接口的接口,这样就可以接入很多其他的前端方案了,因为大多数前端方案都支持OpenAI接口。但是吧,还要维护一个前端项目,很烦人呢,所以我选择了Chainlit,如果你想快速给你的模型接入一个前端,你可以考虑一下Chainlit。
这篇文章我按“真正写项目”的顺序拆成 5 个部分,跟着走一遍,你可以从 0 到 1 跑出一个能用、能迭代、能上线的版本。
为了简单期间,大多数示例都是不接入大模型的,最后会介绍如何接入大模型,接入大模型是一件很简单的事情。总不能只有API KEY才能跑代码,是吧.
第 1 部分:3 分钟跑通最小 Demo
先别想太多,先把服务跑起来。
1) 安装
个人推荐使用uv管理Python项目依赖,避免污染全局Python, 所以后面的命令都是使用uv开头的命令。
uv init
uv add chainlit
2) 自检
uv run chainlit hello
这个命令会拉起一个官方示例页面,能打开就说明安装没问题。
3) 最小应用
新建 app.py:
import chainlit as cl
@cl.on_message
asyncdefmain(message: cl.Message):
"""处理用户消息并返回最小响应。"""
await cl.Message(content=f"收到你的消息了:{message.content}").send()
启动:
chainlit run app.py -w
说明:
-w 会开启热更新,改代码后自动重载,开发体验会好很多。- 默认访问地址是
http://localhost:8000。
一个对话机器人主要的就是对话,所以有个on_message很合理,是不! 就像一个http服务器,用户发送消息,服务器返回消息。只是入口只有一个,就是on_message。
第 2 部分:搞清生命周期和会话管理
很多同学一开始把所有逻辑都塞进 on_message,短期能跑,后面就会乱。
正确姿势是把逻辑按生命周期拆开。
import chainlit as cl
# 窗口打开就给用户发条消息
# 一些资源的初始化可以放这里
@cl.on_chat_start
asyncdefon_chat_start():
"""初始化会话状态,在用户建立新会话时触发。"""
cl.user_session.set("history", [{"role": "system", "content": "你是一个简洁的中文助手。"}])
await cl.Message(content="你好,我已经准备好了,你可以直接提问。").send()
# 回复就回复当前会话消息数
@cl.on_message
asyncdefon_message(message: cl.Message):
"""接收用户消息并写入会话历史。"""
history = cl.user_session.get("history") or []
history.append({"role": "user", "content": message.content})
cl.user_session.set("history", history)
await cl.Message(content=f"当前会话消息数:{len(history)}").send()
# 收尾, 资源回收或日志记录可以放这里
@cl.on_chat_end
defon_chat_end():
"""会话结束时触发,用于资源回收或日志记录。"""
print("聊天会话结束。")
这里重点有 2 个:
- 用
on_chat_start 做初始化,不要在每次消息都初始化。 - 用
cl.user_session 管当前用户会话状态,避免多用户数据串线。
第 3 部分:把体验做对(流式输出 + 交互)
能回消息只是起点,真正好用还得有“正在思考中”的流式体验。
下面给一个通用流式写法(不绑定某个厂商 SDK):
import asyncio
import chainlit as cl
asyncdeffake_llm_stream(text: str):
"""模拟模型按 token 流式输出。"""
for token in text:
await asyncio.sleep(0.1)
yield token + " "
@cl.on_message
asyncdefstream_reply(message: cl.Message):
"""将模型输出以流式方式返回给前端。"""
msg = cl.Message(content="")
await msg.send()
asyncfor token in fake_llm_stream(f"你刚刚说的是:{message.content}"):
await msg.stream_token(token)
await msg.update()
这段代码的价值在于:
- 用户不会“等一个黑屏大响应”,而是能即时看到输出。
- 长回答场景下,体感速度会明显提升。没有哪家模型等结果全出来才生成,是不!
你后续还可以接入:
Step 把中间推理过程展示出来,便于调试和可解释性展示。
第 4 部分:工程化配置与本地调试
当项目从 Demo 进入多人协作,配置和启动参数必须规范。
常用 CLI 参数
# 本地开发 自动检测代码是否变动
chainlit run app.py -w
# 指定监听端口和IP地址, 服务器运行(不自动打开浏览器)
chainlit run app.py --headless --host 0.0.0.0 --port 8000
# 子路径部署(如 https://example.com/chainlit)
# 如果你想不想放在网站的根路径的话
chainlit run app.py -h --root-path /chainlit
几个实践建议:
- 容器部署一般要
--host 0.0.0.0, 不然从容器外映射端口是映射不上的哈!!!
配置还有ssl参数啥的,应该没人在这里配置ssl证书吧。
使用Caddy服务器的反向代理配置
# 假设你有一个Caddyfile,内容如下:
example.com {
reverse_proxy 127.0.0.1:8000
}
第 5 部分:鉴权与上线部署(生产必看)
Chainlit 默认是公开应用。如果你要内网/企业场景,一定要做鉴权。
1) 开启鉴权基础配置
先设置环境变量:
export CHAINLIT_AUTH_SECRET="请替换成你自己的强随机字符串"
也可以用官方命令生成:
chainlit create-secret
2) 增加认证回调(示例:密码模式)
import chainlit as cl
@cl.password_auth_callback
defauth_callback(username: str, password: str):
"""校验用户名密码,成功时返回用户对象。"""
if username == "admin"and password == "123456":
return cl.User(identifier="admin", metadata={"role": "administrator"})
returnNone
上线时再补这几件事:
- 负载均衡开启会话亲和(sticky session)。
- 若多实例部署,优先只走 WebSocket 传输并做好连接稳定性测试。
不存在的第 6 部分
接入langchain还是很简单的,不过需要注意开启langchain的流式输出时,设置正确的参数,参考代码如下
@cl.on_message
asyncdefon_message(message: cl.Message) -> None:
model_key = message.modes.get("model") or DEFAULT_MODEL_KEY
enable_web_search = message.modes.get("search") == "web"
payload = {
"question": message.content,
"model_key": model_key,
"enable_web_search": enable_web_search,
}
msg = cl.Message(content="")
for chunk in graph.stream(
payload,
stream_mode="messages",
version="v2",
config=RunnableConfig(callbacks=[cl.LangchainCallbackHandler()])):
message_chunk, _ = chunk["data"]
if isinstance(message_chunk, AIMessage) and message_chunk.content:
await msg.stream_token(message_chunk.content)
await msg.send()
注意的点如下:
- 流模式下,需要设置
stream_mode="messages",不然不能逐字输出, 等结果在输出体验超级差。 - 流模式下,需要设置
version="v2",配合上面的参数 - 增加回调, 这样chainlit才能在输出中显示经过的步骤。
总结
虽然AI大大加快了写代码的速度,生成前端更不是问题,但是写一个前端和维护一个前端还是不一样的,如果后期不再维护或者修改,那么就直接生成吧,AI应该可以写的很漂亮,也可以自定义各种样式,抄各种显示效果。
但是吧,如果后期需要维护或者修改并且没有太多精力维护,那么就选择一个开源的产品吧,Chainlit实在太棒啦。
官方文档入口:
- https://docs.chainlit.io/get-started/installation
- https://docs.chainlit.io/get-started/pure-python
- https://docs.chainlit.io/concepts/chat-lifecycle
- https://docs.chainlit.io/advanced-features/streaming
- https://docs.chainlit.io/backend/command-line
- https://docs.chainlit.io/authentication/overview
- https://docs.chainlit.io/deploy/overview
LangChain关于流式输出的说明:
- https://docs.langchain.com/oss/python/langchain/streaming