不写前端,也能做出像样的 AI 产品
为什么选 Streamlit
到第 10 篇为止,我们积累的所有能力都是脚本级的——命令行跑一跑看看输出。要把这些能力给真实用户用,必须有 UI。对 Python 开发者而言,这里有三种主流选择:
- Streamlit——声明式,把 Python 脚本自动变网页,10 分钟搞定聊天页。适合内部工具、原型、数据分析应用
- Gradio——AI/ML 社区默认,组件预置了各种多媒体(语音、图像),适合模型 demo
- FastAPI + 前端——生产级,但要你掌握 HTML/JS,学习成本高
Streamlit 对本系列目标读者最合适:零前端知识、十几分钟出成品、够用到部署上线。生产级需求超出 Streamlit 能力范围再升级到 FastAPI + React 也来得及。
最小聊天页
安装:
pip install streamlit
下面这 30 行完整实现一个带流式输出的多轮聊天页:
# app.py
import streamlit as st
from ai import chat_stream, DEFAULT_MODEL # 沿用 02 篇封装
st.set_page_config(page_title="Python AI 聊天助手", page_icon=":robot_face:")
st.title("Python AI 聊天助手")
# session_state 是 Streamlit 存持久状态的地方,每个用户会话独立
if "messages" not in st.session_state:
st.session_state.messages = [
{"role": "system", "content": "你是一位简洁专业的 Python 助手。"}
]
# 把历史消息渲染出来(跳过 system)
for msg in st.session_state.messages:
if msg["role"] == "system":
continue
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
# 底部输入框
if user_input := st.chat_input("有什么我可以帮你的?"):
st.session_state.messages.append({"role": "user", "content": user_input})
with st.chat_message("user"):
st.markdown(user_input)
# 流式渲染助手回复
with st.chat_message("assistant"):
placeholder = st.empty()
full = ""
for piece in chat_stream(st.session_state.messages):
full += piece
placeholder.markdown(full + "▌") # 光标效果
placeholder.markdown(full)
st.session_state.messages.append({"role": "assistant", "content": full})
运行:
streamlit run app.py
浏览器会自动打开 http://localhost:8501,一个功能完整的聊天页就跑起来了——支持多轮上下文、流式打字机效果、会话持久。整个过程你没写一行 HTML/CSS/JS。
理解 Streamlit 的执行模型
Streamlit 有一个非常独特的执行模型,理解它能避免 80% 的诡异 bug:每次用户交互,整个脚本从头到尾重新执行一遍。不是执行某个回调函数,而是把你的 app.py 从第一行重新跑一次。
为什么它看起来还能"保留状态"——全靠 st.session_state。这是一个 dict-like 对象,跨 rerun 保留用户会话的数据。上面代码里的 st.session_state.messages 就是对话历史能持续累积的原因。
这个模型带来几个实用推论:
- 初始化放在
if "xxx" not in st.session_state——保证只在第一次运行时执行 - 昂贵操作用
@st.cache_data或@st.cache_resource缓存——模型加载、数据库连接等只执行一次 - 不要在全局作用域做有副作用的事——会随 rerun 反复触发
# 缓存一个 embedding 模型,避免每次交互都重新加载
@st.cache_resource
def load_embedder():
from sentence_transformers import SentenceTransformer
return SentenceTransformer("BAAI/bge-m3")
embedder = load_embedder() # 第一次真的加载,后续直接拿缓存加侧边栏:模型切换、温度调节、清空对话
真实聊天产品的"配置面板"放在侧边栏里。Streamlit 的 st.sidebar 把任何组件塞进去就行:
with st.sidebar:
st.header("设置")
model = st.selectbox(
"模型",
["deepseek-chat", "deepseek-reasoner", "qwen2.5:7b"],
index=0,
)
temperature = st.slider("temperature", 0.0, 2.0, 0.7, 0.1)
if st.button("清空对话"):
st.session_state.messages = [st.session_state.messages[0]] # 保留 system
st.rerun()
st.rerun() 手动触发一次重新执行,用在"清空"这种需要立即刷新界面的场景。
把 model 和 temperature 传给 chat_stream 就能实时生效。由于每次 rerun 都读最新的侧边栏状态,用户调整参数后下一轮对话自动使用新参数,不需要额外逻辑。
集成 RAG:让助手基于你的文档回答
把 06 篇的 RAG 管线接过来,用户上传文件后索引,提问时自动检索:
# app_rag.py
import streamlit as st
from pathlib import Path
import tempfile
from ai import chat_stream
from indexer import index_directory
from retriever import retrieve
st.set_page_config(page_title="知识库问答", page_icon=":books:")
st.title("知识库问答助手")
# 侧边栏:文件上传和索引
with st.sidebar:
st.header("知识库")
uploaded = st.file_uploader(
"上传文档",
type=["md", "pdf"],
accept_multiple_files=True,
)
if uploaded and st.button("建立索引"):
with st.spinner("正在索引..."):
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
for f in uploaded:
(tmp_path / f.name).write_bytes(f.getbuffer())
index_directory(tmp_path)
st.success(f"已索引 {len(uploaded)} 个文档")
if "messages" not in st.session_state:
st.session_state.messages = [
{"role": "system", "content": "你基于给定的参考资料回答问题,不知道就说不知道。"}
]
for m in st.session_state.messages:
if m["role"] == "system":
continue
with st.chat_message(m["role"]):
st.markdown(m["content"])
if user_input := st.chat_input("针对你的文档问我问题"):
# 先做 RAG 检索
hits = retrieve(user_input, top_k=3)
context = "\n---\n".join(f"[来源:{h['source']}]\n{h['text']}" for h in hits)
augmented = f"参考资料:\n{context}\n\n问题:{user_input}"
st.session_state.messages.append({"role": "user", "content": augmented})
with st.chat_message("user"):
st.markdown(user_input) # UI 只显示原问题
with st.chat_message("assistant"):
placeholder = st.empty()
full = ""
for piece in chat_stream(st.session_state.messages):
full += piece
placeholder.markdown(full + "▌")
placeholder.markdown(full)
# 展示引用来源
with st.expander("查看引用的文档片段"):
for h in hits:
st.caption(f"{h['source']} · 相似度 {h['score']:.3f}")
st.text(h["text"][:300] + "...")
st.session_state.messages.append({"role": "assistant", "content": full})
这个版本已经覆盖了一个知识库问答产品的核心——文件上传、索引、检索、流式回答、引用展示。Python 代码约 60 行,没写一行前端。
注意:user 消息在对话里存的是"含上下文的 augmented 文本",但 UI 上显示的是原始问题。这是工程上常见的做法——让模型看到足够上下文,让用户看到自然问答。
会话持久化
上面的聊天页刷新浏览器就全没了,因为数据只存在 st.session_state 这个内存对象里。持久化很简单——写 JSON 文件或 SQLite:
import json
from pathlib import Path
SESSIONS_DIR = Path("./sessions")
SESSIONS_DIR.mkdir(exist_ok=True)
def save_session(session_id: str, messages: list[dict]):
(SESSIONS_DIR / f"{session_id}.json").write_text(
json.dumps(messages, ensure_ascii=False, indent=2)
)
def load_session(session_id: str) -> list[dict]:
path = SESSIONS_DIR / f"{session_id}.json"
if path.exists():
return json.loads(path.read_text())
return []
侧边栏加一个"历史会话"下拉框,选中时 load_session 恢复;每次对话结束 save_session 存盘。几行代码就有了"ChatGPT 历史对话列表"的效果。
多用户场景要升级到数据库——sqlite3、psycopg、或 supabase-py 都行。Streamlit 本身不管用户认证,多用户要么用 Streamlit Cloud 的内置登录,要么在前面加一层反向代理做 SSO。
部署
Streamlit Community Cloud——免费托管,连 GitHub 仓库直接部署,最适合小规模 demo。限制是计算资源有限,不适合本地模型。
自己部署——最简单方式是 Docker:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8501
CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
扔到任何云主机 / Kubernetes 上都能跑。Streamlit 进程自带健康检查端点 /_stcore/health,方便加负载均衡。
不要把 API Key 写进容器镜像——通过环境变量或 secret 管理器注入。Streamlit 自己也提供 .streamlit/secrets.toml 机制,云端部署时用密钥注入。
Streamlit 的边界
Streamlit 让简单 UI 极速上线,但它不是银弹。下面这些场景它做得很吃力:
- 复杂交互——拖拽、画布、高级动画。Streamlit 的组件库有限
- 极致的性能——rerun 模型有固定开销,高频交互(每秒多次)会卡顿
- 自定义品牌设计——整站风格深度定制需要写 JS 注入,不如直接用 React
- SEO——Streamlit 是动态应用不利于搜索引擎抓取
如果你的产品已经有了用户基础、PMF 明确、需要深度设计,就到了换技术栈的时候——通常是 FastAPI 后端 + Next.js 前端。但在验证阶段 Streamlit 的 ROI 几乎无敌。
本篇要点
- Streamlit 让 Python 开发者零前端做出像样的 AI 聊天应用
- 执行模型是"每次交互重跑整个脚本",状态用
st.session_state保存 - 昂贵操作(模型加载、DB 连接)用
@st.cache_resource缓存 - RAG 集成:上传 → 索引 → 检索 → 流式答 → 展示引用,组合已有模块即可
- 部署首选 Streamlit Cloud(小项目)或 Docker(自主控制)
- 原型阶段用 Streamlit,PMF 之后再换 FastAPI + 前端框架
下一篇
到这里主线 11 篇结束,你已经能独立做出一个完整的 AI 应用。第 12 篇是 进阶方向——LangChain/LlamaIndex 怎么选、评测怎么做、监控用什么工具、微调什么时候值得。把视野从"做出一个 demo"拉高到"把 AI 应用做成可运营的产品"。
参考资料
- Streamlit 官方文档
- Streamlit LLM 示例库
- st.chat_message 文档
- Streamlit Community Cloud
- Chainlit — 专注 AI 对话应用的替代方案,组件更丰富
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:用 Streamlit 构建 AI 聊天应用
本文链接:https://www.sshipanoo.com/blog/ai/ai-for-python/11-Streamlit聊天应用/
本文最后一次更新为 天前,文章中的某些内容可能已过时!