Nanbeige 4.1-3B Streamlit WebUI 添加对话评分与反馈功能
1. 我们要做什么:功能预览与价值
1.1 功能效果长什么样?
想象一下,在原来那个清爽的聊天界面里,每一条 AI 回复的气泡下面,都会多出一行小按钮和输入框:
- :通常是一个'👍'(赞)和一个'👎'(踩),或者用星星、爱心等图标。点击后,按钮状态会变化(比如高亮),表示你已经评过分了。
介绍如何在 Nanbeige 4.1-3B Streamlit WebUI 中集成对话评分与反馈收集功能。通过初始化会话状态存储反馈数据,在 AI 回复下方添加点赞、点踩按钮及输入框,实现用户交互。提交后数据自动保存至本地 JSON 文件,支持后续分析优化模型表现或微调。同时提供了侧边栏导出及样式美化等扩展建议,帮助构建更完善的智能对话应用系统。
想象一下,在原来那个清爽的聊天界面里,每一条 AI 回复的气泡下面,都会多出一行小按钮和输入框:
整个交互过程会非常流畅,就像你在常用的 App 里给服务评价一样自然,完全不会破坏原有聊天界面的美感。
你可能觉得,自己用用而已,要反馈干嘛?其实,这个小小的功能价值很大:
接下来,我们就从最简单的步骤开始,一步步实现它。
在添加新功能之前,我们需要先打开原来的 app.py 文件,快速理解一下它的核心结构。别担心,我们不需要改动那些复杂的 CSS 和流式输出逻辑,只需要找到显示消息和记录历史的地方。
用你的代码编辑器打开 app.py。通常,Streamlit 聊天应用的核心逻辑是遍历一个消息列表,并把每条消息显示出来。这个列表可能叫 messages、chat_history 或者 st.session_state 里的某个变量。
你需要找到类似下面这样的代码块,它负责在页面上渲染每一条对话:
# 示例:原有代码中显示消息的部分可能长这样
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
或者,因为我们的 UI 是高度自定义的,它可能用的是 st.markdown() 配合 CSS 类来渲染气泡。找到显示 AI 回复(role 为 assistant)的那部分代码,这是我们后面要添加评分按钮的地方。
为了把评分和对话关联起来,我们需要知道每条消息的唯一标识。最简单的方法是利用消息在历史列表中的索引(位置)。
检查一下,对话历史是否保存在 st.session_state 中,例如 st.session_state.messages。我们新增的评分数据也会以类似方式存储,比如 st.session_state.feedbacks,它是一个列表,每个元素对应一条 AI 消息的反馈。
做好这些准备后,我们就可以开始动手了。
我们将把实现过程分成三个清晰的步骤:存储反馈数据、在 UI 上添加交互组件、最后保存数据到本地。
首先,我们需要在 Streamlit 的会话状态中开辟一块'地方',用来存放每条 AI 回复收到的评分和反馈。在 app.py 文件的开头部分,初始化会话状态的代码附近,添加以下代码:
# 初始化会话状态(在原有初始化代码旁添加)
if 'messages' not in st.session_state:
st.session_state.messages = [] # 原有的消息历史
if 'feedbacks' not in st.session_state:
st.session_state.feedbacks = [] # 新增:用于存储反馈的列表
# 解释:feedbacks 列表的长度会与 AI 回复的数量对应。
# 每个元素可以是一个字典,例如 {'score': 1, 'comment': '用户输入的文本'}。
这里,feedbacks 列表的索引将隐式地与 messages 列表中 AI 回复的位置对应。例如,第 3 条消息是 AI 回复,那么它的反馈就存在 feedbacks[2] 里(如果索引从 0 开始)。
这是最核心的一步。我们需要修改显示 AI 消息的代码,在每条 AI 回复的内容下方,插入我们的评分按钮和反馈输入框。
找到渲染 AI 消息的代码部分(通常在循环或条件判断中),在其下方添加如下代码。请注意,以下代码需要根据你原有的消息渲染方式进行微调,关键是找到正确的位置插入。
# 假设我们正在遍历并显示消息,`i` 是当前消息的索引
for i, message in enumerate(st.session_state.messages):
if message["role"] == "assistant":
# ... 原有显示 AI 消息内容的代码(例如 st.markdown)...
# --- 新增:评分与反馈区域 ---
# 创建一个紧凑的列布局,将按钮和输入框放在一行
col1, col2, col3 = st.columns([1, 4, 2])
with col1:
# 点赞按钮
if st.button("👍", key=f"like_{i}"):
# 更新反馈状态
st.session_state.feedbacks[i] = {
'score': 1,
'comment': st.session_state.feedbacks[i].get('comment', '') if i < len(st.session_state.feedbacks) else ''
}
st.rerun() # 触发页面刷新,更新按钮状态
# 点踩按钮
if st.button("👎", key=f"dislike_{i}"):
st.session_state.feedbacks[i] = {
'score': -1,
'comment': st.session_state.feedbacks[i].get('comment', '') if i < len(st.session_state.feedbacks) else ''
}
st.rerun()
with col2:
# 反馈输入框
feedback_text = st.text_input(
"您的反馈(可选)",
value=st.session_state.feedbacks[i].get('comment', '') if i < len(st.session_state.feedbacks) else '',
key=f"feedback_input_{i}",
label_visibility="collapsed" # 隐藏标签,更美观
)
# 将输入的文本暂存起来(提交时才正式保存)
# 我们需要一个临时存储,或者直接在点击提交时读取这个值。
with col3:
# 提交按钮
if st.button("提交反馈", key=f"submit_{i}"):
# 确保 feedbacks 列表长度足够
while len(st.session_state.feedbacks) <= i:
st.session_state.feedbacks.append({'score': 0, 'comment': ''})
# 保存评分(如果之前通过点赞/踩按钮设置过)
current_score = st.session_state.feedbacks[i].get('score', 0)
# 更新反馈内容
st.session_state.feedbacks[i] = {'score': current_score, 'comment': feedback_text}
st.success("反馈已保存!")
# 注意:这里不 rerun,否则输入框会失去焦点。可以换成 toast 提示。
代码解释与关键点:
key 参数至关重要:Streamlit 通过 key 来识别不同的组件。我们必须为每个按钮、输入框设置唯一的 key,这里使用 f"like_{i}" 格式,将组件与消息索引 i 绑定,确保每条消息的组件都是独立的。st.session_state.feedbacks 中对应索引的数据,并调用 st.rerun() 刷新界面,这样按钮的视觉状态(后续可通过 CSS 或条件判断来高亮)就能立即变化。while len(st.session_state.feedbacks) <= i 来确保 feedbacks 列表有足够的长度,避免索引错误。反馈数据只存在内存里的话,页面一刷新就没了。我们需要把它持久化保存到本地文件。一个简单的方法是,在每次提交反馈时,或者应用关闭时,将 st.session_state.feedbacks 和对应的消息内容一起保存为 JSON 文件。
我们可以在侧边栏添加一个'导出反馈'按钮,或者自动定时保存。这里我们在提交反馈的代码块内,添加自动保存的逻辑:
# 在'提交反馈'按钮的点击事件处理函数内,更新完 session_state 后,添加保存功能
import json
import os
from datetime import datetime
if st.button("提交反馈", key=f"submit_{i}"):
# ... 上面更新 st.session_state.feedbacks[i] 的代码 ...
# 新增:保存数据到文件
feedback_data = {
"timestamp": datetime.now().isoformat(),
"message_index": i,
"message_content": st.session_state.messages[i]["content"], # 保存对应的消息内容
"feedback": st.session_state.feedbacks[i]
}
# 定义保存文件的路径
save_file = "chat_feedbacks.json"
# 读取现有数据(如果文件存在)
all_feedbacks = []
if os.path.exists(save_file):
try:
with open(save_file, 'r', encoding='utf-8') as f:
all_feedbacks = json.load(f)
except:
all_feedbacks = []
# 添加新反馈(这里简单追加,实际你可能需要去重或更新)
all_feedbacks.append(feedback_data)
# 写回文件
with open(save_file, 'w', encoding='utf-8') as f:
json.dump(all_feedbacks, f, ensure_ascii=False, indent=2)
st.success("反馈已保存至本地文件!")
现在,每次用户提交反馈,都会有一条记录被追加到 chat_feedbacks.json 文件中。这个文件结构清晰,包含了时间戳、消息索引、消息内容和用户反馈,非常适合后续分析。
基础功能已经实现了,但我们可以让它更好用、更好看。
现在点击👍/👎按钮后,除了页面刷新一下,用户可能不知道是否成功。我们可以通过改变按钮的样式来反馈状态。
Streamlit 的 st.button 本身不支持动态样式,但我们可以用一点小技巧:根据 st.session_state.feedbacks[i]['score'] 的值,来决定显示哪个按钮被'激活'。一个简单的方法是使用 st.markdown 配合 HTML/CSS 来创建更灵活的按钮,但这稍复杂。
更简单实用的方法是用条件判断来替换按钮文字或提示。例如,如果已经评过分,就把按钮变成不可点击状态或显示已选:
# 简化版思路:在 col1 内部
score = st.session_state.feedbacks[i].get('score', 0) if i < len(st.session_state.feedbacks) else 0
with col1:
if score == 1:
st.markdown("**👍 已赞**") # 或者用一个禁用的按钮
else:
if st.button("👍", key=f"like_{i}"):
# ... 更新逻辑 ...
if score == -1:
st.markdown("**👎 已踩**")
else:
if st.button("👎", key=f"dislike_{i}"):
# ... 更新逻辑 ...
我们可以把数据管理功能放到 Streamlit 的侧边栏里,让界面更整洁。
添加侧边栏导出按钮:在侧边栏(通常用 with st.sidebar: 创建)添加一个按钮,点击后将当前的 st.session_state.feedbacks 和 st.session_state.messages 一起导出为一个 JSON 文件,并提供下载链接。
with st.sidebar:
st.header("反馈数据管理")
if st.button("导出所有反馈数据"):
# 组合消息和反馈数据
export_data = {
"messages": st.session_state.messages,
"feedbacks": st.session_state.feedbacks
}
# 转换为 JSON 字符串
import json
json_str = json.dumps(export_data, ensure_ascii=False, indent=2)
# 提供下载
st.download_button(
label="下载 JSON 文件",
data=json_str,
file_name="chat_feedback_export.json",
mime="application/json"
)
为了让新增的反馈区域和原有的极简二次元风格更融合,我们可以注入一点 CSS 来调整样式。例如,让输入框更小,按钮更圆润。
在 app.py 中查找注入 CSS 的地方(通常是一个 st.markdown("<style>...</style>", unsafe_allow_html=True)),在原有样式后面添加:
/* 微调反馈输入框样式 */
.stTextInput>div>div>input { font-size: 0.9em; padding: 4px 8px; }
/* 微调提交按钮样式 */
.stButton>button { font-size: 0.9em; padding: 0.25em 0.75em; }
恭喜你!现在你的 Nanbeige 4.1-3B Streamlit WebUI 已经拥有了一个完整的对话评分与反馈收集系统。让我们回顾一下核心步骤:
st.session_state.feedbacks 来存储数据。st.columns 布局添加了👍/👎按钮、文本输入框和提交按钮。key,并将其与消息索引绑定。扩展思路:
这个基础框架可以玩出很多花样:
现在,运行你的 app.py,和 Nanbeige 模型聊聊天,然后试试新加的评分功能吧。这些收集来的真实反馈,会成为你优化对话体验的宝贵指南针。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online