Nanbeige 4.1-3B Streamlit WebUI保姆级教程:添加对话评分与反馈收集功能
Nanbeige 4.1-3B Streamlit WebUI保姆级教程:添加对话评分与反馈收集功能
你是不是也遇到过这种情况?用自己部署的大模型对话应用,聊得挺开心,但总觉得少了点什么。比如,你没法告诉模型“刚才这个回答真棒”或者“这个回答好像不太对”,更没法把这些宝贵的反馈记录下来,用来优化模型或者改进应用。
今天,我们就来解决这个问题。我将手把手教你,如何为之前介绍的“南北阁 (Nanbeige) 4.1-3B Streamlit WebUI”添加一个既实用又美观的对话评分与反馈收集功能。这个功能不仅能让你在对话中随时点赞或点踩,还能让你输入具体的反馈意见,所有数据都会自动保存下来,方便后续分析。
学完这篇教程,你就能拥有一个带“用户反馈系统”的智能对话应用了。无论是自己用,还是分享给朋友,体验都会提升一个档次。
1. 我们要做什么:功能预览与价值
在开始敲代码之前,我们先来看看最终要实现的效果,以及它到底有什么用。
1.1 功能效果长什么样?
想象一下,在原来那个清爽的聊天界面里,每一条AI回复的气泡下面,都会多出一行小按钮和输入框:
- 评分按钮:通常是一个“👍”(赞)和一个“👎”(踩),或者用星星、爱心等图标。点击后,按钮状态会变化(比如高亮),表示你已经评过分了。
- 反馈输入框:一个不那么显眼的小文本框,你可以在这里输入具体的意见,比如“这个例子举得很好”、“这里的信息过时了”或者“请用更简单的语言解释”。
- 提交按钮:输入反馈后,点击提交,这条评分和反馈就会和对应的对话记录关联起来,被保存到文件或数据库里。
整个交互过程会非常流畅,就像你在常用的App里给服务评价一样自然,完全不会破坏原有聊天界面的美感。
1.2 为什么需要这个功能?
你可能觉得,自己用用而已,要反馈干嘛?其实,这个小小的功能价值很大:
- 帮你优化提示词:如果你发现模型在某些问题上总是回答不好,通过反馈可以定位到是哪些类型的问题,从而有针对性地改进你的提问方式或系统提示。
- 评估模型表现:长期收集的评分数据,可以直观地告诉你,这个模型在你关心的任务上,整体表现如何,是越来越好,还是存在某些固有缺陷。
- 为模型微调提供数据:如果你未来打算用自己的数据对模型进行微调,这些带有正负向标注(赞/踩)和具体原因(反馈文本)的对话记录,就是极其宝贵的训练数据。
- 提升应用交互感:给用户一个表达的出口,会让应用感觉更完整、更专业,也更能吸引用户持续使用。
接下来,我们就从最简单的步骤开始,一步步实现它。
2. 前期准备:理解原有代码结构
在添加新功能之前,我们需要先打开原来的 app.py 文件,快速理解一下它的核心结构。别担心,我们不需要改动那些复杂的CSS和流式输出逻辑,只需要找到显示消息和记录历史的地方。
2.1 找到消息显示的核心循环
用你的代码编辑器打开 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”)的那部分代码,这是我们后面要添加评分按钮的地方。
2.2 确认会话状态存储
为了把评分和对话关联起来,我们需要知道每条消息的唯一标识。最简单的方法是利用消息在历史列表中的索引(位置)。
检查一下,对话历史是否保存在 st.session_state 中,例如 st.session_state.messages。我们新增的评分数据也会以类似方式存储,比如 st.session_state.feedbacks,它是一个列表,每个元素对应一条AI消息的反馈。
做好这些准备后,我们就可以开始动手了。
3. 分步实现:添加评分与反馈组件
我们将把实现过程分成三个清晰的步骤:存储反馈数据、在UI上添加交互组件、最后保存数据到本地。
3.1 第一步:初始化反馈数据存储
首先,我们需要在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开始)。
3.2 第二步:在AI回复后添加交互UI
这是最核心的一步。我们需要修改显示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列表有足够的长度,避免索引错误。
3.3 第三步:保存反馈数据到本地文件
反馈数据只存在内存里的话,页面一刷新就没了。我们需要把它持久化保存到本地文件。一个简单的方法是,在每次提交反馈时,或者应用关闭时,将 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 文件中。这个文件结构清晰,包含了时间戳、消息索引、消息内容和用户反馈,非常适合后续分析。
4. 功能优化与美化建议
基础功能已经实现了,但我们可以让它更好用、更好看。
4.1 优化一:让评分按钮状态更直观
现在点击👍/👎按钮后,除了页面刷新一下,用户可能不知道是否成功。我们可以通过改变按钮的样式来反馈状态。
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}"): # ... 更新逻辑 ... 4.2 优化二:集成到侧边栏进行管理
我们可以把数据管理功能放到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" ) 4.3 优化三:使用CSS微调样式
为了让新增的反馈区域和原有的极简二次元风格更融合,我们可以注入一点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; } 5. 总结与扩展思路
恭喜你!现在你的Nanbeige 4.1-3B Streamlit WebUI已经拥有了一个完整的对话评分与反馈收集系统。让我们回顾一下核心步骤:
- 规划与准备:明确了要添加评分按钮、反馈输入框和数据存储功能。
- 理解结构:找到了原有代码中显示消息和存储历史的关键部分。
- 实现核心:
- 初始化了
st.session_state.feedbacks来存储数据。 - 在每条AI回复下方,使用
st.columns布局添加了👍/👎按钮、文本输入框和提交按钮。 - 为每个组件设置了唯一的
key,并将其与消息索引绑定。 - 实现了点击按钮和提交时的状态更新逻辑。
- 初始化了
- 数据持久化:在提交反馈时,将数据(包含时间戳、消息索引、内容、评分和文本反馈)追加保存到本地的JSON文件中。
- 优化体验:探讨了如何可视化评分状态、如何将管理功能集成到侧边栏,以及如何用CSS进行样式微调。
扩展思路:
这个基础框架可以玩出很多花样:
- 多维度评分:不止赞/踩,可以增加“有帮助”、“有创意”、“准确性”等多个维度进行打分。
- 反馈模板:提供一些常用的反馈短语作为快速选择按钮,如“信息不准确”、“表达不清晰”、“例子很好”等,降低用户输入成本。
- 数据可视化:在侧边栏增加一个仪表盘,用图表展示近期对话的评分趋势、正面/负面反馈比例等。
- 关联分析:分析低评分主要集中在哪些类型的问题上,从而优化你的系统提示词(Prompt)。
现在,运行你的 app.py,和Nanbeige模型聊聊天,然后试试新加的评分功能吧。这些收集来的真实反馈,会成为你优化对话体验的宝贵指南针。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 ZEEKLOG星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。