你费劲写好了一个能调用工具、会查资料的 Python 智能体,结果只能在终端里一行行敲命令,发给同事还得教他装环境——这体验太劝退。Chainlit 就是来解决这个问题的:它用纯 Python 给你生成一套网页聊天界面,消息流、打字效果、工具调用步骤、文件上传、多轮记忆全都有,而且不需要写一行前端。
先搞懂:Chainlit 在 Agent 链路里站在哪一层?
一张图看懂分工:你的智能体逻辑(调模型、调工具)是「大脑」,Chainlit 是「脸面」。它不管你怎么实现智能体,只负责把用户的消息接进来、把你的回复和中间步骤展示出去。
| 组件 | 作用 | 本教程是否涉及 |
|---|---|---|
| 大模型客户端 | 真正生成回答 | ✅ OpenAI / 任意兼容客户端 |
| 你的 Agent 逻辑 | 组提示词、调工具 | ✅ 写在 app.py 里 |
| Chainlit | 网页界面与交互 | ✅ 主角 |
定位边界:Chainlit 不是另一个 Agent 框架,它不替你做编排。你完全可以用 LangChain、OpenAI Agents SDK 甚至手写循环,只要把结果交给 Chainlit 展示就行。本文用最朴素的 openai 调用,把重点放在界面本身。
Step 1:安装并跑起第一个 Chainlit 应用
新建一个项目目录,装好 chainlit,再写一个能原样回消息的 app.py。这是验证环境的最小闭环。
pip install chainlit openai python-dotenv
# app.py
import chainlit as cl
@cl.on_message
async def main(message: cl.Message):
# 先把用户说的话原样发回去,确认界面能跑
await cl.Message(content=message.content).send()
在项目目录里启动(加 -w 会自动打开浏览器):
chainlit run app.py -w
Step 2:接一个大模型,让 Agent 能真正回答
把 main 里的回显换成一次大模型调用。这里用 OpenAI 兼容接口,换成任意厂商都可以。
import os
from dotenv import load_dotenv
import chainlit as cl
from openai import OpenAI
load_dotenv()
client = OpenAI()
@cl.on_message
async def main(message: cl.Message):
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": message.content}],
)
answer = r.choices[0].message.content
await cl.Message(content=answer).send()
报错了? 多半是没设 OPENAI_API_KEY 环境变量,或网络访问 API 被墙。把密钥写进 .env(OPENAI_API_KEY=sk-xxx),国内访问官方 API 通常需要代理。换个能用的模型端点也是常规操作。
Step 3:用 @cl.step 把工具调用变成可见的步骤
真实 Agent 常要先查资料再回答。用 @cl.step 装饰一个函数,界面就会把它显示成一条独立的步骤,调试和演示都更清楚。
@cl.step(type="tool")
def search_knowledge(q: str) -> str:
# 这里接你自己的检索 / 工具,返回一段文本
return "(示例)在资料库中找到:Chainlit 用 @cl.on_message 接收消息。"
@cl.on_message
async def main(message: cl.Message):
snippet = search_knowledge(message.content)
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user",
"content": snippet + "\n\n问题:" + message.content}],
)
await cl.Message(content=r.choices[0].message.content).send()
Step 4:用 cl.user_session 记住对话历史(记忆)
默认每次消息都是独立会话。用 user_session 把历史存起来,下次消息带着上下文一起发,Agent 才像在「连续对话」。
@cl.on_message
async def main(message: cl.Message):
history = cl.user_session.get("history", [])
history.append({"role": "user", "content": message.content})
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=history,
)
reply = r.choices[0].message.content
history.append({"role": "assistant", "content": reply})
cl.user_session.set("history", history)
await cl.Message(content=reply).send()
别忘了截断:history 会越攒越长,最终超出模型上下文。生产环境记得只保留最近 N 轮,或做摘要压缩。本文演示用,先不处理。
Step 5:加文件上传,让 Agent 读你给的文档
Chainlit 原生支持文件上传。用户在聊天框传文件后,message.elements 里就能拿到本地路径,读出来再喂给模型。
@cl.on_message
async def main(message: cl.Message):
text = message.content
for f in (message.elements or []):
with open(f.path, "r", encoding="utf-8", errors="ignore") as fh:
text = text + "\n\n文档内容:\n" + fh.read()[:4000]
r = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": text}],
)
await cl.Message(content=r.choices[0].message.content).send()
Step 6:本地运行与分享
演示用本地即可;要给别人用,有两条路:Chainlit 官方云(一键部署,但内容会公开)或自托管(放你自己的服务器,数据不出去)。
# 方式一:本地开发
chainlit run app.py -w
# 方式二:部署到 Chainlit Cloud(需先 chainlit login)
chainlit deploy
# 方式三:自托管(放内网 / 自己的服务器)
# 用 chainlit run app.py 起服务,
# 前面套一层 Nginx 或反向代理并加鉴权
安全红线:chainlit deploy 会把应用公开到官方云,不要放含密钥或隐私的演示。对内网自用,务必在反向代理层加登录或 IP 白名单,别把没鉴权的聊天界面直接暴露到公网。
常见问题速查
| 现象 | 原因 与 解决 |
|---|---|
| chainlit 命令不存在 | 没装包或装到了别的 Python 环境,确认 pip 和运行的 python 是同一个 |
| 界面打不开 localhost:8000 | 端口被占,用 chainlit run app.py -p 8001 换端口 |
| 模型调用报错 | 检查 OPENAI_API_KEY 与网络,或换成可用的模型端点 |
| 上传文件读不到 | 确认用 message.elements 且文件类型在允许列表,路径用 f.path |