Qwen3-0.6B-FP8教程:Chainlit前端添加用户身份识别、对话历史持久化(SQLite)
Qwen3-0.6B-FP8教程Chainlit前端添加用户身份识别、对话历史持久化SQLite你是不是也遇到过这样的问题用Chainlit搭建了一个漂亮的AI对话前端但每次刷新页面之前的聊天记录就全没了或者当多个用户同时使用时你根本分不清谁是谁对话全都混在一起今天我们就来解决这两个痛点。我将手把手教你如何为基于Qwen3-0.6B-FP8模型和Chainlit的前端添加用户身份识别和对话历史持久化功能。我们会用最简单、最轻量的SQLite数据库来存储数据让你轻松实现“记住用户”和“记住对话”。学完这篇教程你的AI应用将不再是“健忘”的临时工而是一个能记住每个用户、每次对话的“贴心助手”。无论是个人使用还是小团队协作体验都会大幅提升。1. 环境准备与项目回顾在开始动手之前我们先快速回顾一下你的项目环境确保我们站在同一起跑线上。1.1 确认你的项目状态根据你提供的描述你的项目应该已经成功部署了以下组件模型服务使用vLLM部署的Qwen3-0.6B-FP8文本生成模型。这是一个非常高效的部署方式能提供快速的推理响应。前端界面使用Chainlit构建的Web对话界面。Chainlit是一个专门为AI应用设计的Python框架能快速搭建出交互性很强的聊天界面。基础连接Chainlit前端已经能够成功调用后端的Qwen3模型进行对话。你可以通过以下命令再次确认模型服务是否正常运行cat /root/workspace/llm.log如果看到模型加载成功的日志信息说明后端一切就绪。1.2 我们需要做什么目前你的Chainlit应用是“无状态”的。这意味着没有用户识别所有访问者都被视为同一个“匿名用户”。没有历史记录页面一刷新或者关闭浏览器标签之前的对话就消失了。我们的目标是通过修改Chainlit的代码实现用户身份识别为每个访问者生成或分配一个唯一的用户ID。对话持久化将每次的对话内容用户提问和AI回答保存到SQLite数据库中。历史记录加载当用户再次访问时能加载他之前的对话历史。听起来是不是很有用我们马上开始。2. 核心概念理解Chainlit的会话与用户在写代码之前我们需要理解Chainlit中两个关键概念这是我们实现功能的基础。2.1 会话Session在Chainlit中每次用户打开一个新的浏览器标签页访问你的应用就会创建一个独立的会话。这个会话对象通常叫cl.user_session就像一个临时的储物柜你可以在里面存放一些只在这个标签页生命周期内有效的数据比如当前对话的上下文。但是这个“储物柜”是临时的。关闭标签页里面的东西就清空了。所以我们不能只靠会话来长期保存对话历史。2.2 用户User与用户会话User SessionChainlit提供了更强大的cl.UserSession对象。它和一次页面访问绑定但更重要的是它内置了用户身份标识的概念。我们可以通过cl.user_session.get(user)来获取一个代表当前用户的对象。这个对象有一个id属性它就是实现用户识别的关键。默认情况下Chainlit会为每个匿名访问者生成一个随机的唯一ID。我们的核心思路就是利用这个user.id作为数据库中的用户标识符将对话记录和这个ID关联起来。这样即使用户换了浏览器或者清了缓存只要这个ID机制能让他被识别为“同一个用户”我们就能找回他的历史记录。3. 分步实现添加数据库与业务逻辑理论清楚了现在开始写代码。我们会一步步修改你的Chainlit应用文件通常是app.py。3.1 第一步安装依赖并设计数据库首先确保你的环境有必要的库。SQLite是Python内置的我们只需要安装用于数据库操作的sqlite3通常已内置和可能用到的uuid库。pip install uuid接下来我们在项目根目录创建一个名为database.py的文件专门处理所有数据库操作。我们先设计一张简单的表来存储对话记录。# database.py import sqlite3 import uuid from datetime import datetime from typing import List, Optional, Tuple class ChatDatabase: def __init__(self, db_path: str chat_history.db): 初始化数据库连接并创建表如果不存在 self.db_path db_path self._init_db() def _init_db(self): 创建对话历史表 conn sqlite3.connect(self.db_path) cursor conn.cursor() # 创建表。我们存储记录ID、用户ID、角色用户/助手、消息内容、时间戳。 cursor.execute( CREATE TABLE IF NOT EXISTS chat_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, role TEXT NOT NULL, -- user 或 assistant content TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP ) ) # 为了快速按用户查询历史我们在user_id上创建索引 cursor.execute(CREATE INDEX IF NOT EXISTS idx_user_id ON chat_messages(user_id)) conn.commit() conn.close() def save_message(self, user_id: str, role: str, content: str): 保存一条消息到数据库 conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute( INSERT INTO chat_messages (user_id, role, content) VALUES (?, ?, ?), (user_id, role, content) ) conn.commit() conn.close() def get_user_history(self, user_id: str, limit: int 20) - List[Tuple[str, str]]: 获取某个用户最近的对话历史。 返回一个列表列表中的每个元素是 (role, content) 元组。 conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute( SELECT role, content FROM chat_messages WHERE user_id ? ORDER BY timestamp ASC LIMIT ? , (user_id, limit) ) history cursor.fetchall() conn.close() return history def clear_user_history(self, user_id: str): 清除某个用户的所有对话历史可选功能 conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute(DELETE FROM chat_messages WHERE user_id ?, (user_id,)) conn.commit() conn.close()这个ChatDatabase类封装了所有数据库操作结构清晰使用起来也很简单。3.2 第二步改造Chainlit主应用现在我们来修改Chainlit的主文件。假设你原来的app.py大概长这样一个简化版# app.py - 原始版本简化 import chainlit as cl import requests import json cl.on_chat_start async def start_chat(): # 初始化一些设置 settings { model: Qwen3-0.6B-FP8, temperature: 0.7, } cl.user_session.set(settings, settings) cl.on_message async def main(message: cl.Message): # 1. 获取用户消息 user_msg message.content # 2. 调用你的vLLM后端API # 注意这里的URL需要替换成你实际的vLLM服务地址 api_url http://localhost:8000/v1/completions # 示例地址 headers {Content-Type: application/json} data { model: Qwen3-0.6B-FP8, prompt: user_msg, max_tokens: 512, temperature: 0.7, } response requests.post(api_url, headersheaders, datajson.dumps(data)) if response.status_code 200: result response.json() ai_response result[choices][0][text] else: ai_response f请求模型API时出错: {response.status_code} # 3. 发送AI的回复 await cl.Message(contentai_response).send()我们需要将它升级集成用户识别和历史记录功能。# app.py - 升级版本集成用户识别与历史持久化 import chainlit as cl import requests import json from database import ChatDatabase # 导入我们刚写的数据库类 # 初始化数据库 db ChatDatabase() cl.on_chat_start async def start_chat(): 当聊天开始时触发。 1. 获取或创建用户标识。 2. 从数据库加载该用户的历史对话。 3. 将历史记录设置到会话中用于构建上下文。 # 获取当前用户会话。Chainlit会为每个浏览器会话管理一个user。 user cl.user_session.get(user) if user: user_id user.id print(f聊天开始用户ID: {user_id}) else: # 理论上Chainlit总会提供user这里做安全处理 user_id anonymous_user print(未获取到用户信息使用匿名ID) # 将用户ID存入当前会话方便后续使用 cl.user_session.set(user_id, user_id) # 从数据库加载该用户的历史消息例如最近10轮对话 history_messages db.get_user_history(user_id, limit20) # 获取最多20条历史记录 # 将历史记录也存入会话可以用来构建上下文提示如果需要 cl.user_session.set(chat_history, history_messages) # 可选在界面上给用户一个提示告诉他历史记录已加载 if history_messages: # 简单计算一下历史对话轮数每2条为一轮用户助手 num_turns len([msg for msg in history_messages if msg[0] user]) welcome_msg f欢迎回来已加载您最近的 {num_turns} 轮对话历史。 else: welcome_msg 您好我是基于Qwen3-0.6B-FP8模型的AI助手开始我们的对话吧 await cl.Message(contentwelcome_msg).send() # 初始化模型设置保持不变 settings { model: Qwen3-0.6B-FP8, temperature: 0.7, } cl.user_session.set(settings, settings) cl.on_message async def main(message: cl.Message): 处理用户发送的每一条消息。 1. 保存用户消息到数据库。 2. 调用模型获取回复。 3. 保存AI回复到数据库。 4. 发送回复给用户。 # 1. 获取用户ID和消息内容 user_id cl.user_session.get(user_id) user_msg message.content # 2. 将用户消息保存到数据库 db.save_message(user_id, user, user_msg) print(f已保存用户消息到数据库。用户ID: {user_id}) # 3. 调用vLLM后端API这里是你原有的逻辑 api_url http://localhost:8000/v1/completions # 请替换为你的实际地址 headers {Content-Type: application/json} # 构建请求数据。你可以根据需要决定是否将历史记录作为上下文传入。 # 以下是一个简单的示例只发送当前消息。 data { model: Qwen3-0.6B-FP8, prompt: user_msg, max_tokens: 512, temperature: 0.7, } # 如果你想利用历史记录构建更丰富的上下文可以这样做 # history cl.user_session.get(chat_history, []) # # 将历史记录格式化成模型接受的提示格式这里需要根据你的模型调整 # full_prompt format_history_to_prompt(history, new_messageuser_msg) # data[prompt] full_prompt response requests.post(api_url, headersheaders, datajson.dumps(data)) if response.status_code 200: result response.json() ai_response result[choices][0][text] else: ai_response f抱歉处理您的请求时遇到了问题错误码{response.status_code}。 # 4. 将AI助手的回复保存到数据库 db.save_message(user_id, assistant, ai_response) print(f已保存AI回复到数据库。用户ID: {user_id}) # 5. 发送回复给前端 await cl.Message(contentai_response).send()3.3 第三步增强功能 - 添加上下文和历史记录管理上面的代码实现了基本的存储和加载。但一个更友好的AI助手应该能“记住”之前的对话内容让对话更连贯。我们可以通过将历史记录作为上下文传给模型来实现。在app.py中添加一个辅助函数并修改main函数中的API调用部分# 在 app.py 中添加的辅助函数 def build_prompt_with_history(history: List[Tuple[str, str]], new_user_message: str) - str: 将历史对话和新的用户消息构建成一个完整的提示。 这是一个简单示例格式需要根据你使用的模型进行调整。 Qwen系列模型通常使用类似以下的对话格式 |im_start|user\n历史用户消息|im_end|\n|im_start|assistant\n历史AI回复|im_end|\n... |im_start|user\n新的用户消息|im_end|\n|im_start|assistant\n prompt_parts [] for role, content in history: # 根据角色添加对应的标签 if role user: prompt_parts.append(f|im_start|user\n{content}|im_end|) else: # assistant prompt_parts.append(f|im_start|assistant\n{content}|im_end|) # 添加新的用户消息 prompt_parts.append(f|im_start|user\n{new_user_message}|im_end|) # 最后添加assistant的开始标签引导模型开始生成 prompt_parts.append(|im_start|assistant\n) return \n.join(prompt_parts) # 修改 cl.on_message 函数中的API调用部分 cl.on_message async def main(message: cl.Message): user_id cl.user_session.get(user_id) user_msg message.content db.save_message(user_id, user, user_msg) # --- 修改部分开始构建包含历史的提示 --- history cl.user_session.get(chat_history, []) # 将当前用户消息也临时加入历史列表用于构建提示但先不保存因为还没获取到AI回复 full_history history [(user, user_msg)] # 构建包含上下文的提示词 full_prompt build_prompt_with_history(full_history, user_msg) # 注意这里函数可能需要调整以适配新参数 # 更合理的函数调用可能是build_prompt_with_history(full_history) # 我们调整一下函数定义和调用 # def build_prompt_from_history(history: List[Tuple[str, str]]) - str: # ... # 内部逻辑 # full_prompt build_prompt_from_history(full_history) # 简化版如果我们不区分角色标签只是简单拼接历史 context \n.join([f{role}: {content} for role, content in full_history[-6:]]) # 取最近3轮对话6条消息 enhanced_prompt f以下是之前的对话\n{context}\n\n用户最新消息{user_msg}\n\n请根据以上对话历史回复用户的最新消息 data { model: Qwen3-0.6B-FP8, prompt: enhanced_prompt, # 使用增强了上下文的提示 max_tokens: 512, temperature: 0.7, } # --- 修改部分结束 --- # ... 其余代码保持不变发送请求、保存AI回复、返回消息 response requests.post(api_url, headersheaders, datajson.dumps(data)) if response.status_code 200: result response.json() ai_response result[choices][0][text] else: ai_response f请求出错: {response.status_code} db.save_message(user_id, assistant, ai_response) # 更新会话中的历史记录列表可选用于下次构建上下文 # 注意这里我们只保存在内存会话中持久化已经由数据库负责。 updated_history history [(user, user_msg), (assistant, ai_response)] # 保持历史记录不会无限增长例如只保留最近10轮 if len(updated_history) 20: # 保留最多20条消息10轮对话 updated_history updated_history[-20:] cl.user_session.set(chat_history, updated_history) await cl.Message(contentai_response).send()注意上面构建上下文提示词 (enhanced_prompt) 的方式是一个通用示例。对于Qwen模型你可能需要查阅其官方文档使用其特定的对话模板如apply_chat_template函数来获得最佳效果。核心思想是将历史对话和当前问题一起送给模型。4. 运行与验证代码写完了我们来测试一下功能是否正常。4.1 启动你的应用确保你的vLLM模型服务已经在运行通常在端口8000。然后在终端中进入你的项目目录启动Chainlit应用chainlit run app.pyChainlit会在默认端口通常是8000或7860启动一个Web服务。用浏览器打开它提供的地址如http://localhost:8000。4.2 功能验证首次对话打开页面你应该能看到欢迎信息。发送一条消息AI会回复。此时检查你的项目目录下应该新生成了一个chat_history.db文件。查看数据库你可以用任何SQLite工具如命令行sqlite3 chat_history.db或图形化工具DB Browser for SQLite打开这个数据库文件执行SELECT * FROM chat_messages;应该能看到刚刚保存的对话记录并且每条记录都关联着一个长长的用户ID字符串。历史加载关键测试完全关闭浏览器或者打开一个无痕/隐私窗口再次访问你的Chainlit应用地址。由于Chainlit默认会为匿名用户生成一个持久化的标识存储在浏览器本地你很可能不会看到“欢迎回来”的提示因为在新会话中user.id可能变了。这说明我们依赖的默认匿名ID机制在浏览器层面可能不够持久。4.3 解决“用户识别”的持久性问题上面的测试暴露了一个问题Chainlit为匿名用户生成的ID可能随着浏览器会话如隐私模式、清除缓存而变化。对于需要真正“记住用户”的生产环境我们需要更稳定的用户标识。这里有几个改进方向方案A要求用户登录最可靠使用Chainlit的认证功能让用户通过用户名/密码或其他方式如OAuth登录。登录后我们可以使用用户名或邮箱作为稳定的user_id。方案B使用浏览器指纹折中方案通过JavaScript收集浏览器的一些不变信息如User Agent, 屏幕分辨率, 时区等生成一个“指纹”作为用户ID。但这并非100%唯一和稳定。方案C前端传递用户标识简单实用在Chainlit中我们可以在前端通过cl.send_message或自定义元素在聊天开始时就将一个标识符比如从URL参数获取或前端生成并存储在LocalStorage的UUID发送到后端。这里给出一个方案C的简化示例在前端通过LocalStorage存储一个用户UUID并在每次会话开始时发送给后端首先你需要在Chainlit的index.html自定义前端或通过cl.set_starting_message注入JavaScript来设置和发送这个ID。但更简单的方式是在cl.on_chat_start中尝试从cl.user_session中获取一个由前端传入的ID。实际上Chainlit的cl.user_session可以存储任何序列化数据。一种常见模式是让前端在连接建立后立即发送一个包含用户ID的特殊消息或元数据。然而Chainlit的公开API对此支持有限。一个更直接的实践建议对于教程和大多数需要“记住对话”但非严格用户管理的场景依赖Chainlit默认的匿名用户ID机制并接受其在清除浏览器数据后会失效的现实通常是可以接受的。因为我们的主要目标是“在同一个浏览器会话中保持历史”而Chainlit默认已经通过LocalStorage在一定程度上做到了这一点。我们上面实现的数据库持久化主要解决了“服务端重启后历史记录不丢失”的问题。如果你观察到在普通浏览器标签页中刷新页面后历史记录能加载那么基础功能就已经实现了。5. 总结与进阶思考恭喜你你已经成功为你的Qwen3-0.6B-FP8 Chainlit应用加上了“记忆”功能。让我们回顾一下核心成果数据持久化所有对话现在都安全地存储在SQLite数据库 (chat_history.db) 中服务重启也不会丢失。用户关联每条对话记录都通过user_id与一个用户标识关联尽管匿名标识的长期稳定性有限。历史上下文通过从数据库加载历史记录并构建到提示词中让AI模型能够进行连贯的多轮对话。代码结构清晰我们将数据库操作封装在独立的database.py中使主应用逻辑保持整洁。5.1 可以继续优化的方向你的AI助手现在更聪明了但还有提升空间优化上下文构建根据Qwen3模型的官方文档使用其推荐的apply_chat_template方法来格式化历史对话效果会更好。添加管理功能写一个简单的管理页面可以用Flask或直接写个Python脚本来查看所有用户的对话记录或者清理旧数据。探索更稳定的用户标识如前面讨论的研究Chainlit的认证机制实现真正的用户登录系统。性能与扩展如果用户量变大可以考虑将SQLite升级为PostgreSQL或MySQL并引入连接池。丰富对话体验利用Chainlit的更多功能如文件上传、步骤式交互、自定义UI元素等让你的前端更加好用。5.2 最终建议对于个人项目或小范围使用的工具本文实现的基于SQLite和Chainlit默认会话的方案已经非常实用。它极大地提升了产品的可用性和用户体验让AI对话从“一次性快照”变成了“连续的记事本”。现在你的AI应用已经具备了核心的“记忆”能力。快去和你的Qwen3助手多聊几句看看它是不是更能理解你的上下文了获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。