构建个人大模型问答助手(基于Streamlit +gpt-4o/o1-mini):全面解析与实现
在当今人工智能迅猛发展的时代,构建一个个人化的大模型问答助手不仅能够提高工作效率,还能为日常生活带来便利。本篇博客将详细解析如何使用Python和Streamlit框架,结合OpenAI的API,搭建一个类似于ChatGPT的问答系统。我们将分步骤介绍代码实现,同时确保敏感信息的安全性。
基于ManyiAPI聚合接口站:https://api.manyi88.top, ManyiAPI注册链接(注册优惠) 可以直接调用国内外语言大模型,实现自己的问答助手,效果如图:
项目代码地址:
https://gitcode.com/sequoia00/appChat_streamlit/overview
目录
- 项目概述
- 环境准备
- 代码实现详解
- 导入必要的库
- API密钥与模型配置
- 界面构建
- 数据管理
- 历史记录管理
- 聊天功能实现
- 辅助功能
- 安全性与隐私保护
- 总结
- 参考资料
项目概述
本项目旨在通过Python和Streamlit框架,结合OpenAI的API,构建一个个人化的问答助手。该助手允许用户选择不同的语言模型,与之进行对话,同时管理聊天历史记录,实现保存、加载、备份和删除功能。通过这一项目,用户可以体验到类似于ChatGPT的对话体验,并根据需求进行定制。
环境准备
在开始编码之前,确保你的开发环境中已经安装了以下软件和库:
- Python 3.7+
- Streamlit:用于构建Web应用的框架。
- OpenAI Python SDK:用于与OpenAI的API进行交互。
- 其他辅助库:如
json
,os
,glob
,re
,shutil
等。
可以使用以下命令安装必要的库:
pip install streamlit openai
代码实现详解
下面,我们将对整个代码进行逐段详细解析,帮助读者深入理解每一部分的功能与实现。
导入必要的库
from openai import OpenAI
import streamlit as st
import json
import os
import glob
import re
import shutil # 用于文件移动
解析:
这些库为项目提供了必要的功能支持:
- openai:与OpenAI API交互。
- streamlit:构建用户界面。
- json:处理JSON数据格式。
- os, glob, re, shutil:文件和目录管理。
API密钥与模型配置
# 示例:模型和相应的 API 密钥
default_key = "sk-***" # 默认令牌
sale_key = "sk-***" # 自定义默认1.0
guan_key = "sk-***" # 管转令牌3倍
az_key = "sk-***" # 纯AZ,1.5倍
claude_key = "sk-***" # claude 8倍
guan5_key = "sk-***" # 管转令牌5倍model_keys = {"gpt-4o-mini-2024-07-18": az_key, #1.5"o1-mini": guan5_key, #3"gpt-4o-2024-08-06": az_key,"claude-3-5-sonnet-20240620": claude_key
}
解析:
- API密钥管理:为不同的模型配置不同的API密钥,以实现对不同服务的调用和控制。
- 模型选择:通过
model_keys
字典,将模型名称与对应的API密钥关联,方便用户在界面上选择。
安全建议:
将API密钥硬编码在代码中存在安全风险。建议使用环境变量或配置文件来存储这些密钥,并在代码中通过读取环境变量的方式获取,以避免泄露风险。
界面构建
为了方便调用模型,可以访问ManyiAPI聚合接口站:https://api.manyi88.top, ManyiAPI注册链接(注册优惠)
st.title("ChatGPT-like Clone")
selected_model = st.sidebar.selectbox("选择模型", list(model_keys.keys()))
api_key = model_keys[selected_model]
api_url = "https://api.manyi88.top/v1" #ManyiAPI聚合接口站:https://api.manyi88.topclient = OpenAI(api_key=api_key, base_url=api_url)
解析:
- 标题设置:通过
st.title
函数设置应用的标题。 - 模型选择:在侧边栏提供一个下拉菜单,用户可以选择不同的模型。
- API客户端初始化:根据用户选择的模型,获取对应的API密钥和API URL,初始化OpenAI客户端。
数据管理
data_dir = "data"
backup_dir = "data_bak"
if not os.path.exists(data_dir):os.makedirs(data_dir)
if not os.path.exists(backup_dir):os.makedirs(backup_dir)
解析:
- 数据目录:
data_dir
用于存储聊天记录。 - 备份目录:
backup_dir
用于备份历史聊天记录。 - 目录检查与创建:如果目录不存在,自动创建,确保文件操作的顺利进行。
会话ID管理
session_id_file = os.path.join(data_dir, "session_id.txt")def load_session_id():if os.path.exists(session_id_file):with open(session_id_file, "r") as f:return int(f.read().strip())return 0def save_session_id(session_id):with open(session_id_file, "w") as f:f.write(str(session_id))
解析:
- 会话ID文件:用于存储当前会话的ID。
- 加载会话ID:如果文件存在,读取当前的会话ID;否则,初始化为0。
- 保存会话ID:在新会话开始时,将会话ID保存到文件中,确保会话的持续性和唯一性。
历史记录管理
加载历史记录
def load_history(file_path):match = re.search(r'chat_history_(\\d+)\\.json', os.path.basename(file_path))if match:st.session_state.session_id = int(match.group(1))with open(file_path, "r") as f:st.session_state.messages = json.load(f)
解析:
- 文件名匹配:通过正则表达式提取会话ID。
- 加载消息:从JSON文件中读取聊天记录,更新会话状态。
加载最新历史
def load_latest_history():history_files = sorted(glob.glob(os.path.join(data_dir, "*.json")), key=os.path.getmtime)if history_files:latest_file = history_files[-1]load_history(latest_file)
解析:
- 获取所有历史文件:使用
glob
获取data_dir
目录下所有JSON文件。 - 排序与加载:按修改时间排序,加载最新的聊天记录。
聊天记录展示
def get_chat_title(messages):if messages:first_message = messages[0]["content"]title = first_message if first_message else "空的聊天"return title[:12]return "空的聊天"[:12]
解析:
- 标题生成:根据聊天记录的第一条消息生成聊天标题,截取前12个字符。
历史文件展示与管理
def show_history_files(page=0, page_size=10):history_files = [(f, os.path.getmtime(f)) for f in glob.glob(os.path.join(data_dir, "*.json"))]history_files.sort(key=lambda x: x[1], reverse=True)total_files = len(history_files)total_pages = (total_files // page_size) + (1 if total_files % page_size > 0 else 0)start = page * page_sizeend = start + page_sizedisplay_files = history_files[start:end]with st.sidebar.expander("历史聊天记录"):for index, (file_path, _) in enumerate(display_files):with open(file_path, "r") as f:chat_history = json.load(f)file_name = get_chat_title(chat_history)col1, col2, col3 = st.sidebar.columns([4, 1, 1]) # 创建三列with col1:if st.button(file_name, key=f"load_{index}"):load_history(file_path)st.rerun()with col2:if st.button("📦", key=f"move_{index}", help="移动到备份文件夹"):move_history(file_path)st.success(f"{file_name} 已移动到备份。")st.rerun()with col3:if st.button("❌", key=f"delete_{index}", help="删除"):delete_history(file_path)st.success(f"{file_name} 已删除。")st.rerun()# 分页if page > 0:if st.button("上一页"):st.session_state.current_page -= 1st.rerun()if page < total_pages - 1:if st.button("下一页"):st.session_state.current_page += 1st.rerun()
解析:
- 文件排序:按修改时间降序排列聊天记录文件。
- 分页展示:每页显示10个文件,提供“上一页”和“下一页”按钮进行导航。
- 文件操作:
- 加载:点击文件名按钮加载对应的聊天记录。
- 移动:将文件移动到备份文件夹。
- 删除:删除指定的聊天记录文件。
聊天界面与输入
for message in st.session_state.messages:with st.chat_message(message["role"]):st.markdown(message["content"])output_mode = st.sidebar.selectbox("选择输出模式", ["流式输出 (Stream)", "非流式输出 (Non-stream)"])
解析:
- 消息展示:遍历
st.session_state.messages
,根据消息角色(用户或助手)展示对应内容。 - 输出模式选择:用户可选择使用流式输出(即时显示)或非流式输出(一次性显示)的模式。
历史数量选择
# 初始化 session_state 中的历史数量
if "history_count" not in st.session_state:st.session_state.history_count = 0# 在侧边栏添加滑动条
if len(st.session_state.messages) == 0:max_history_count = 10
else:max_history_count = len(st.session_state.messages)st.sidebar.slider("选择使用的历史消息数量(共" + str(len(st.session_state.messages)) + "条)",min_value=0,max_value=max_history_count,value=st.session_state.history_count, # 默认值key="history_count" # 使用一个唯一的键来存储选择值
)# 显示当前选择的历史消息数量
st.sidebar.write(f"您选择的历史消息数量是: {st.session_state.history_count}")
解析:
- 历史消息数量选择:通过滑动条让用户选择使用的历史消息数量,以影响生成新回复时的上下文范围。
- 默认值与限制:如果当前无消息,最大历史数量设为10;否则,最大值为现有消息数量。
聊天输入与响应
prompt = st.chat_input("What is up?")
if prompt:st.session_state.messages.append({"role": "user", "content": prompt})with st.chat_message("user"):st.markdown(prompt)with st.chat_message("assistant"):client.api_key = model_keys[selected_model]stream = output_mode == "流式输出 (Stream)"try:if st.session_state.history_count > 0:messages_to_send = ([{"role": m["role"], "content": m["content"]}for m in st.session_state.messages[-st.session_state.history_count:]])else:messages_to_send = [{"role": "user", "content": prompt}]res = client.chat.completions.create(model=selected_model,messages=messages_to_send,stream=stream,)if stream:assistant_message = st.write_stream(res)if assistant_message:st.session_state.messages.append({"role": "assistant", "content": assistant_message})else:st.warning("收到空响应。")else: if len(res.choices) > 0:assistant_message = res.choices[0].message.contentif assistant_message:st.markdown(assistant_message)st.session_state.messages.append({"role": "assistant", "content": assistant_message})else:st.warning("收到空响应。")else:st.warning("响应格式不正确,未找到有效消息。")save_history()except Exception as e:st.error(f"发生错误:{e}")
解析:
-
用户输入:
- 获取用户通过
st.chat_input
输入的文本。 - 将用户消息添加到
st.session_state.messages
中,并在界面上展示。
- 获取用户通过
-
助手响应:
- 根据用户选择的输出模式,设置是否采用流式输出。
- 构建发送给API的消息列表:
- 如果选择使用历史消息,则提取最新的
history_count
条消息。 - 否则,仅发送当前的用户输入。
- 如果选择使用历史消息,则提取最新的
- 调用OpenAI API生成回应:
- 流式输出:逐步显示助手的回复。
- 非流式输出:一次性显示完整的回复。
- 将助手的回复添加到
st.session_state.messages
中,并保存聊天记录。
-
错误处理:
- 捕获并显示在请求过程中发生的任何异常,确保用户能够及时了解问题。
辅助功能实现
新建聊天会话
st.sidebar.header("操作")
if st.sidebar.button("New Chat"):st.session_state.messages = [] # 清空当前会话st.session_state.session_id = load_session_id()st.session_state.session_id += 1save_session_id(st.session_state.session_id)st.success("当前会话已清空。")
解析:
- 按钮功能:点击“New Chat”按钮,清空当前的聊天记录,并生成一个新的会话ID。
- 状态更新:更新
st.session_state.messages
和st.session_state.session_id
,并提示用户会话已重置。
历史对话展示
st.sidebar.header("历史对话")
show_history_files(st.session_state.current_page)
解析:
- 展示历史对话:调用之前定义的
show_history_files
函数,在侧边栏展示历史聊天记录,并提供相应的管理操作(加载、移动、删除)。
安全性与隐私保护
在构建和部署个人问答助手时,安全性和隐私保护至关重要。以下是一些关键点:
-
API密钥管理:
- 避免硬编码:API密钥不应直接写在代码中,尤其是在版本控制系统中。
- 使用环境变量:推荐将密钥存储在环境变量中,通过代码读取,增强安全性。
import os api_key = os.getenv("OPENAI_API_KEY")
- 配置文件:另一个选择是使用配置文件,将敏感信息存储在外部配置文件中,并在
.gitignore
中排除该文件。
-
数据存储:
- 加密:敏感的聊天记录应进行加密存储,防止未经授权的访问。
- 访问控制:确保只有授权用户能够访问和操作聊天记录。
-
错误处理:
- 详细信息:在生产环境中,不应向用户展示过于详细的错误信息,以防泄露系统内部信息。
- 日志记录:将错误信息记录在安全的日志系统中,便于后续分析。
-
用户隐私:
- 数据最小化:仅收集和存储必要的用户数据,避免冗余信息的存储。
- 用户同意:在收集和使用用户数据前,需获得用户明确的同意,并提供隐私政策说明。
总结
本文详细介绍了如何使用Python和Streamlit框架,结合OpenAI的API,构建一个个人化的大模型问答助手。从环境准备、代码结构解析到具体实现步骤,我们逐步解析了每一部分的功能与实现细节。同时,强调了在开发过程中需注意的安全性与隐私保护措施,确保项目的稳健和可靠性。
通过这一项目,读者不仅可以学习到如何搭建一个高效的问答系统,还能深入理解如何管理API密钥、处理数据存储以及实现用户界面交互。希望本篇博客能为您的AI项目提供有价值的参考和指导。
参考资料
- ManyiAPI注册链接(注册优惠)
- Streamlit 官方文档
- OpenAI API 文档
- Python 官方文档
注意事项
感谢您的阅读和支持。在代码实施过程中,请务必确保API密钥和其他敏感信息的安全,避免意外泄露。如发现密钥泄露,请立即撤销并生成新的密钥,以保障您的账户安全。