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 为什么需要这个功能?

你可能觉得,自己用用而已,要反馈干嘛?其实,这个小小的功能价值很大:

  1. 帮你优化提示词:如果你发现模型在某些问题上总是回答不好,通过反馈可以定位到是哪些类型的问题,从而有针对性地改进你的提问方式或系统提示。
  2. 评估模型表现:长期收集的评分数据,可以直观地告诉你,这个模型在你关心的任务上,整体表现如何,是越来越好,还是存在某些固有缺陷。
  3. 为模型微调提供数据:如果你未来打算用自己的数据对模型进行微调,这些带有正负向标注(赞/踩)和具体原因(反馈文本)的对话记录,就是极其宝贵的训练数据。
  4. 提升应用交互感:给用户一个表达的出口,会让应用感觉更完整、更专业,也更能吸引用户持续使用。

接下来,我们就从最简单的步骤开始,一步步实现它。

2. 前期准备:理解原有代码结构

在添加新功能之前,我们需要先打开原来的 app.py 文件,快速理解一下它的核心结构。别担心,我们不需要改动那些复杂的CSS和流式输出逻辑,只需要找到显示消息和记录历史的地方。

2.1 找到消息显示的核心循环

用你的代码编辑器打开 app.py。通常,Streamlit聊天应用的核心逻辑是遍历一个消息列表,并把每条消息显示出来。这个列表可能叫 messageschat_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的侧边栏里,让界面更整洁。

  1. 添加清空反馈按钮:同样在侧边栏,可以添加一个清空当前会话反馈的按钮(注意不是清空聊天记录)。

添加侧边栏导出按钮:在侧边栏(通常用 with st.sidebar: 创建)添加一个按钮,点击后将当前的 st.session_state.feedbacksst.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已经拥有了一个完整的对话评分与反馈收集系统。让我们回顾一下核心步骤:

  1. 规划与准备:明确了要添加评分按钮、反馈输入框和数据存储功能。
  2. 理解结构:找到了原有代码中显示消息和存储历史的关键部分。
  3. 实现核心
    • 初始化了 st.session_state.feedbacks 来存储数据。
    • 在每条AI回复下方,使用 st.columns 布局添加了👍/👎按钮、文本输入框和提交按钮。
    • 为每个组件设置了唯一的 key,并将其与消息索引绑定。
    • 实现了点击按钮和提交时的状态更新逻辑。
  4. 数据持久化:在提交反馈时,将数据(包含时间戳、消息索引、内容、评分和文本反馈)追加保存到本地的JSON文件中。
  5. 优化体验:探讨了如何可视化评分状态、如何将管理功能集成到侧边栏,以及如何用CSS进行样式微调。

扩展思路

这个基础框架可以玩出很多花样:

  • 多维度评分:不止赞/踩,可以增加“有帮助”、“有创意”、“准确性”等多个维度进行打分。
  • 反馈模板:提供一些常用的反馈短语作为快速选择按钮,如“信息不准确”、“表达不清晰”、“例子很好”等,降低用户输入成本。
  • 数据可视化:在侧边栏增加一个仪表盘,用图表展示近期对话的评分趋势、正面/负面反馈比例等。
  • 关联分析:分析低评分主要集中在哪些类型的问题上,从而优化你的系统提示词(Prompt)。

现在,运行你的 app.py,和Nanbeige模型聊聊天,然后试试新加的评分功能吧。这些收集来的真实反馈,会成为你优化对话体验的宝贵指南针。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 ZEEKLOG星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Read more

AI入门系列:AI入门者的困惑:常见术语解释与误区澄清

AI入门系列:AI入门者的困惑:常见术语解释与误区澄清

引言 人工智能领域充满了令人困惑的专业术语和概念误区。对于刚接触AI的新手而言,机器学习、深度学习、神经网络这些名词常常让人一头雾水。很多初学者会将AI简单地等同于机器人,或者误以为AI已经具备人类水平的思维能力。实际上,AI是一个包含多个子领域的广阔学科,每个术语都有其特定的含义和应用范围。理解这些基础概念的区别,避免常见的认知误区,是踏入AI世界的第一步。本文将系统梳理AI领域的核心术语,澄清普遍存在的误解,帮助初学者建立正确的认知框架,为后续的深入学习打下坚实基础。 AI到底是什么?从科幻到现实的转变 很多人一听到AI,就想到《终结者》里的天网或者《黑客帝国》里的矩阵。但实际上,AI远比这些科幻场景要"接地气"得多。 想象一下,当你对手机说"嘿,Siri,明天天气怎么样?",手机能够理解你的话,查找天气信息,并用语音回答你。这就是AI在工作,它包含了语音识别、自然语言处理、信息检索等多个技术。 AI的本质是让机器完成那些过去只有人类才能完成的任务。但这并不意味着机器要变得像人一样思考,而是让机器在特定任务上表现得像人一样聪明。 误区澄清:

(第四篇)Spring AI 实战进阶:Ollama+Spring AI 构建离线私有化 AI 服务(脱离 API 密钥的完整方案)

(第四篇)Spring AI 实战进阶:Ollama+Spring AI 构建离线私有化 AI 服务(脱离 API 密钥的完整方案)

前言 作为企业级开发者,我们在使用大模型时常常面临三大痛点:依赖第三方 API 密钥导致的成本不可控、外网依赖导致的合规风险、用户数据上传第三方平台导致的安全隐患。尤其是金融、政务等敏感行业,离线私有化部署几乎是硬性要求。 笔者近期基于 Ollama+Spring AI 完成了一套离线 AI 服务的落地,从模型拉取、量化优化到 RAG 知识库构建全程无外网依赖,彻底摆脱了 API 密钥的束缚。本文将从实战角度,完整拆解离线 AI 服务的开发全流程:包含 Ollama 部署、Spring AI 深度对接、模型量化优化、离线 RAG 知识库落地,所有代码均经过生产环境验证,同时结合可视化图表清晰呈现核心逻辑,希望能为企业级离线 AI 部署提供可落地的参考方案。 一、项目背景与技术选型 1.1 核心痛点与解决方案 业务痛点解决方案技术选型依赖第三方

国内主流AI工具对比 - 豆包、元宝、千问、Kimi、DeepSeek、MiniMax、GLM

国内主流AI工具对比 - 豆包、元宝、千问、Kimi、DeepSeek、MiniMax、GLM AI生成,仅供参考 引言 在AI技术快速发展的今天,国内涌现出了众多优秀的AI工具。本文将对比分析国内主流的7款AI工具:豆包、元宝、千问、Kimi、DeepSeek、MiniMax、GLM,帮助你选择最适合自己的AI工具。 工具概览 工具开发公司主要特点适用场景豆包字节跳动功能全面、响应快速、免费使用快速问答、写作辅助、翻译需求元宝腾讯视频会议AI助手、实时字幕、会议纪要视频会议、客户沟通、在线培训千问阿里云强大的中文理解能力、多模态支持深度对话、写作辅助、代码开发KimiMoonshot AI超长上下文、文档处理能力长文档处理、学术研究、知识管理DeepSeekDeepSeek AI代码能力强、推理能力强、开源代码开发、深度分析、技术研究MiniMaxMiniMax多模态能力强、创意生成内容创作、创意生成、娱乐互动GLM智谱AI学术背景强、中文理解好学术研究、知识问答、

2598.从效率瓶颈到批量创作:文心一言多线程写作辅助工具的开发与实践

2598.从效率瓶颈到批量创作:文心一言多线程写作辅助工具的开发与实践

在内容创作领域,批量产出优质内容始终是从业者面临的重要挑战。尤其是需要基于同一平台进行多账号操作、多主题创作时,重复的手动操作不仅耗费时间,更会大幅降低创作效率。 作为一名长期从事内容生产工具开发的程序员,我和团队近期完成了一款针对文心一言平台的多线程批量写作辅助工具,希望通过技术手段解决这些实操痛点。 2598.操作演示视频 开发初衷:拆解创作流程中的效率卡点 在实际运营中,我们发现内容创作者在使用文心一言时常常陷入三重困境:一是多账号切换繁琐,每次登录都需要重新验证;二是主题管理混乱,大量创作方向难以系统化调度;三是批量操作耗时,单线程模式下完成数十篇内容创作往往需要数小时。 基于这些真实需求,我们决定开发一款工具,核心目标并非替代人工创作,而是通过自动化技术解决重复性操作问题。工具的底层逻辑遵循 "人机协同" 原则 —— 机器负责处理登录、输入、提交等机械步骤,人类创作者则专注于内容构思与质量把控。 技术架构:模块化设计的实践思路 整个工具采用模块化架构,将功能拆解为界面交互层、核心控制层和数据存储层三个部分,这种设计既保证了各功能模块的独立性,也为后续扩展提供