
本文提供一种符合 Streamlit 官方最佳实践的流式聊天实现方式,通过 st.chat_message + st.write_stream 替代手动 st.empty() 刷新,彻底解决 Linux/Windows 下滚动跳动、页面重置、光标错位等常见渲染异常问题。
本文提供一种符合 streamlit 官方最佳实践的流式聊天实现方式,通过 `st.chat_message` + `st.write_stream` 替代手动 `st.empty()` 刷新,彻底解决 linux/windows 下滚动跳动、页面重置、光标错位等常见渲染异常问题。
在 Streamlit 中构建 LLM 流式聊天应用时,频繁调用 st.empty().write() 或手动触发 st.rerun() 是导致滚动抖动(scroll jitter)的根本原因——尤其在 Linux 系统上更为明显。这是因为每次 st.rerun() 都会重建整个页面 DOM 树,强制浏览器重计算布局与滚动锚点;而 st.empty() 的反复覆盖又破坏了
✅ 正确做法是:完全遵循 Streamlit 官方推荐的 st.chat_message + st.write_stream 模式,让框架自动管理消息容器生命周期与滚动行为。
以下为重构后的核心实现(已验证跨平台兼容):
CentOS Stream 9是基于RHEL 9技术路线的持续交付版本,适合需要贴近RHEL 9生态的软件开发、系统集成和测试环境。它相比传统CentOS Linux更靠近上游开发过程,用户可以更早看到RHEL 9后续小版本中的软件包变化。CentOS Stream 9仍是当前可用的官方版本线之一,适合对稳定性和新功能之间有平衡需求的团队使用。
import streamlit as st
def general_chat(user_input):
# 构建上下文与指令(保持逻辑不变)
key_data = ["politics", "religion", "violence"]
blocked_topics = "\n".join([f"Do not answer any questions about: {k}" for k in key_data])
history_context = "\n".join(
f"{msg['role']}: {msg['content']}" for msg in st.session_state.chat_history
)
prompt_input = (
f"{history_context}\n"
f"Instructions:\n{blocked_topics}\n"
f"User: {user_input}\n"
f"Assistant:"
)
# ✅ 关键改进:使用 st.chat_message 包裹 + write_stream 流式输出
with st.chat_message("assistant"):
# 假设你使用 ollama、langchain 或自定义流式生成器
def response_generator():
for chunk in llm.stream(prompt_input): # 替换为你的实际流式调用
yield chunk
response = st.write_stream(response_generator())
# ✅ 同步更新历史记录(注意字段名统一为 'content')
st.session_state.chat_history.append({"role": "assistant", "content": response})
def chat_page():
st.title("ThinkBot")
st.write("Welcome to ThinkBot! I am an AI Chatbot.")
# 初始化会话历史(务必使用 'content' 字段,与 st.chat_message 兼容)
if "chat_history" not in st.session_state:
st.session_state.chat_history = [
{"role": "assistant", "content": "How can I help you?"}
]
# ✅ 渲染历史消息:每条消息独立包裹在 st.chat_message 中
for msg in st.session_state.chat_history:
with st.chat_message(msg["role"]):
st.markdown(msg["content"])
# ✅ 处理新输入:无需 st.rerun(),直接追加并调用响应函数
if user_input := st.chat_input(placeholder="Your message"):
# 立即显示用户消息
st.session_state.chat_history.append({"role": "user", "content": user_input})
with st.chat_message("user"):
st.markdown(user_input)
# 直接调用流式响应(不触发 rerun)
general_chat(user_input)
? 关键注意事项:
- 禁止手动 st.rerun():st.chat_input 触发后,所有后续操作(显示用户消息、调用 general_chat)均在单次脚本运行中完成,避免 DOM 重建;
- 统一字段命名:st.session_state.chat_history 中每条消息必须含 "role" 和 "content"(非 "message"),否则 st.chat_message 渲染可能异常;
- 禁用自定义 CSS 干预滚动:如 .stAppScrollToBottomContainer { overflow-anchor: none !important; } 可能干扰 Streamlit 内部滚动锚定机制,应移除;
- 流式生成器必须可迭代:确保 llm.stream(...) 返回的是生成器或可迭代对象,st.write_stream() 依赖其逐块消费;
-
Linux 特别提示:部分 Linux 桌面环境(如 GNOME + Wayland)对滚动事件更敏感,本方案通过框架原生滚动容器()规避底层差异。
? 总结:Streamlit 的 st.chat_message 不仅是 UI 组件,更是滚动状态管理器。放弃手动控制 DOM,信任框架对聊天场景的深度优化,是解决所有滚动抖动问题的最简、最稳路径。










