实战-AI智能伴侣-会话管理

实战-AI智能伴侣-会话管理

2026年9月18日·#编程学习/python学习笔记Python/Streamlit/文件操作·2716 字 14 分钟
浏览量加载中...
AI 摘要

把会话存进文件实现"关掉网页还能接着聊":新建/保存/加载/删除会话,以及 os、json、datetime 的配合使用

要解决的问题#

前面几节的聊天记录只存在 st.session_state 里——内存中存放的数据在计算机关机后就会消失,要永久保存数据,就需要将数据保存在文件中。

于是侧边栏要提供四个功能:

功能说明
新建会话保存当前会话 → 清空聊天 → 生成新的会话标识
保存会话每轮对话结束后自动保存一份
加载历史会话点历史列表里的某一项,把它的内容读回来
删除历史会话删掉对应的文件

每个会话文件存什么#

思考题:每一个历史会话文件中,需要保存哪些信息呢?

字段含义
messages交互消息(完整的对话历史,会话记忆的基础)
nick_name昵称
nature性格
current_session会话标识(名字,唯一)

存成 JSON 文件的样子(sessions/2026-01-11_18-00-05.json):

{
"messages": [
{ "role": "user", "content": "你好" },
{ "role": "assistant", "content": "哈囉~今天過得怎麼樣呀?💕" }
],
"nick_name": "小美",
"nature": "温柔可爱的台湾腔姑娘",
"current_session": "2026-01-09_16-05-20"
}

文件目录结构:

sessions
|- 2026-01-11_18-00-05.json
|- 2026-01-11_18-04-42.json
|- 2026-01-11_18-08-56.json
|- 2026-01-11_18-14-25.json
|- 2026-01-11_18-45-08.json
Tip

为什么要保存 nick_namenature?因为它们是生成 system 提示词的参数。只恢复聊天记录、不恢复伴侣设定的话,性格就变了——同一份对话换个性格继续聊,会很割裂。

五个核心函数#

1. 生成会话标识#

from datetime import datetime
def generate_session_name():
return datetime.now().strftime("%Y-%m-%d_%H-%M-%S")

2. 保存会话#

import os
import json
def save_session():
if st.session_state.current_session:
# 构建新的会话对象
session_data = {
"nick_name": st.session_state.nick_name,
"nature": st.session_state.nature,
"current_session": st.session_state.current_session,
"messages": st.session_state.messages
}
# 如果 sessions 目录不存在, 则创建
if not os.path.exists("sessions"):
os.mkdir("sessions")
# 保存会话数据
with open(f"sessions/{st.session_state.current_session}.json", "w", encoding="utf-8") as f:
json.dump(session_data, f, ensure_ascii=False, indent=2)

3. 加载会话列表#

def load_sessions():
session_list = []
# 加载sessions目录下的文件
if os.path.exists("sessions"):
file_list = os.listdir("sessions")
for filename in file_list:
if filename.endswith(".json"):
session_list.append(filename[:-5]) # 去掉末尾的 .json
session_list.sort(reverse=True) # 排序, 降序排列(最新的在最上面)
return session_list

4. 加载指定会话#

def load_session(session_name):
try:
if os.path.exists(f"sessions/{session_name}.json"):
# 读取会话数据
with open(f"sessions/{session_name}.json", "r", encoding="utf-8") as f:
session_data = json.load(f)
st.session_state.messages = session_data["messages"]
st.session_state.nick_name = session_data["nick_name"]
st.session_state.nature = session_data["nature"]
st.session_state.current_session = session_name
except Exception:
st.error("加载会话失败!")

5. 删除会话#

def delete_session(session_name):
try:
if os.path.exists(f"sessions/{session_name}.json"):
os.remove(f"sessions/{session_name}.json") # 删除文件
# 如果删除的是当前会话, 则需要更新消息列表
if session_name == st.session_state.current_session:
st.session_state.messages = []
st.session_state.current_session = generate_session_name()
except Exception:
st.error("删除会话失败!")

侧边栏界面#

with st.sidebar: # with 是 streamlit 中的上下文管理器
st.subheader("AI控制面板")
# 新建会话
if st.button("新建会话", width="stretch", icon="✏️"):
# 1. 保存当前会话信息
save_session()
# 2. 创建新的会话
if st.session_state.messages: # 如果聊天信息非空, True; 否则, False
st.session_state.messages = []
st.session_state.current_session = generate_session_name()
save_session()
st.rerun() # 重新运行当前页面
# 会话历史
st.text("会话历史")
session_list = load_sessions()
for session in session_list:
col1, col2 = st.columns([4, 1])
with col1:
# 加载会话信息
# 三元运算符: 值1 if 条件 else 值2
if st.button(session, width="stretch", icon="📄", key=f"load_{session}",
type="primary" if session == st.session_state.current_session else "secondary"):
load_session(session)
st.rerun()
with col2:
# 删除会话信息
if st.button("", width="stretch", icon="❌️", key=f"delete_{session}"):
delete_session(session)
st.rerun()
st.divider()
# 伴侣信息
st.subheader("伴侣信息")
nick_name = st.text_input("昵称", placeholder="请输入昵称", value=st.session_state.nick_name)
if nick_name:
st.session_state.nick_name = nick_name
nature = st.text_area("性格", placeholder="请输入性格", value=st.session_state.nature)
if nature:
st.session_state.nature = nature

关键点#

代码为什么必须这么写
key=f"load_{session}"Streamlit 里每个按钮都需要唯一的 key。循环生成的按钮如果 key 相同会报 DuplicateWidgetID,把会话名拼进去即可保证唯一
type="primary" if ... else "secondary"三元运算符:当前会话的按钮高亮成主色,一眼能看出”现在在哪一个会话里”
st.rerun()按钮点击后数据已改变,需要让脚本立刻重跑一遍,把新状态渲染出来
st.columns([4, 1])把一行按 4:1 分成两列:左边放会话名按钮(宽),右边放删除按钮(窄)
width="stretch"按钮宽度撑满所在列(旧写法 use_container_width=True 已废弃)
Note

with st.sidebar: 用的是 Python 的上下文管理器语法。它和 with open(...) 是同一个语法机制:进入代码块时切到侧边栏,退出时切回主区域。同理 with col1: 表示”接下来的组件放进第一列”。

Warning

save_session() 里判断目录要用 os.path.exists("sessions"),不能每次都 os.mkdir——目录已存在时 os.mkdir 会抛 FileExistsError

数据流转全景#

启动 ──> 初始化 session_state(messages / nick_name / nature / current_session)
├─ 侧边栏点「新建会话」 ──> save_session() 存盘 ──> 清空 ──> 新标识 ──> st.rerun()
├─ 侧边栏点某条历史 ──> load_session() 读盘 ──> 覆盖 session_state ──> st.rerun()
├─ 侧边栏点 ❌ ──> delete_session() 删文件 ──> st.rerun()
└─ 输入框发消息 ──> 调用大模型(流式) ──> append 进 messages ──> save_session()

完整代码(最终版)#

import streamlit as st
import os
from openai import OpenAI
from datetime import datetime
import json
# 设置页面的配置项
st.set_page_config(
page_title="AI智能伴侣",
page_icon="🤖",
layout="wide",
initial_sidebar_state="expanded",
menu_items={}
)
# 生成会话标识函数
def generate_session_name():
return datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
# 保存会话信息函数
def save_session():
if st.session_state.current_session:
session_data = {
"nick_name": st.session_state.nick_name,
"nature": st.session_state.nature,
"current_session": st.session_state.current_session,
"messages": st.session_state.messages
}
if not os.path.exists("sessions"):
os.mkdir("sessions")
with open(f"sessions/{st.session_state.current_session}.json", "w", encoding="utf-8") as f:
json.dump(session_data, f, ensure_ascii=False, indent=2)
# 加载所有的会话列表信息
def load_sessions():
session_list = []
if os.path.exists("sessions"):
file_list = os.listdir("sessions")
for filename in file_list:
if filename.endswith(".json"):
session_list.append(filename[:-5])
session_list.sort(reverse=True)
return session_list
# 加载指定的会话信息
def load_session(session_name):
try:
if os.path.exists(f"sessions/{session_name}.json"):
with open(f"sessions/{session_name}.json", "r", encoding="utf-8") as f:
session_data = json.load(f)
st.session_state.messages = session_data["messages"]
st.session_state.nick_name = session_data["nick_name"]
st.session_state.nature = session_data["nature"]
st.session_state.current_session = session_name
except Exception:
st.error("加载会话失败!")
# 删除会话信息函数
def delete_session(session_name):
try:
if os.path.exists(f"sessions/{session_name}.json"):
os.remove(f"sessions/{session_name}.json")
if session_name == st.session_state.current_session:
st.session_state.messages = []
st.session_state.current_session = generate_session_name()
except Exception:
st.error("删除会话失败!")
# 大标题 / Logo
st.title("AI智能伴侣")
st.logo("resources/logo.png")
# 系统提示词
system_prompt = """
你叫 %s,现在是用户的真实伴侣,请完全代入伴侣角色。
规则:
1. 每次只回1条消息
2. 禁止任何场景或状态描述性文字
3. 匹配用户的语言
4. 回复简短,像微信聊天一样
5. 有需要的话可以用❤️🌸等emoji表情
6. 用符合伴侣性格的方式对话
7. 回复的内容, 要充分体现伴侣的性格特征
伴侣性格:
- %s
你必须严格遵守上述规则来回复用户。
"""
# 初始化会话状态
if "messages" not in st.session_state:
st.session_state.messages = []
if "nick_name" not in st.session_state:
st.session_state.nick_name = "小甜甜"
if "nature" not in st.session_state:
st.session_state.nature = "活泼开朗的东北姑娘"
if "current_session" not in st.session_state:
st.session_state.current_session = generate_session_name()
# 展示聊天信息
st.text(f"会话名称: {st.session_state.current_session}")
for message in st.session_state.messages:
st.chat_message(message["role"]).write(message["content"])
# 创建与AI大模型交互的客户端对象
client = OpenAI(api_key=os.environ.get('DEEPSEEK_API_KEY'), base_url="https://api.deepseek.com")
# 左侧的侧边栏
with st.sidebar:
st.subheader("AI控制面板")
# 新建会话
if st.button("新建会话", width="stretch", icon="✏️"):
save_session()
if st.session_state.messages:
st.session_state.messages = []
st.session_state.current_session = generate_session_name()
save_session()
st.rerun()
# 会话历史
st.text("会话历史")
session_list = load_sessions()
for session in session_list:
col1, col2 = st.columns([4, 1])
with col1:
if st.button(session, width="stretch", icon="📄", key=f"load_{session}",
type="primary" if session == st.session_state.current_session else "secondary"):
load_session(session)
st.rerun()
with col2:
if st.button("", width="stretch", icon="❌️", key=f"delete_{session}"):
delete_session(session)
st.rerun()
st.divider()
# 伴侣信息
st.subheader("伴侣信息")
nick_name = st.text_input("昵称", placeholder="请输入昵称", value=st.session_state.nick_name)
if nick_name:
st.session_state.nick_name = nick_name
nature = st.text_area("性格", placeholder="请输入性格", value=st.session_state.nature)
if nature:
st.session_state.nature = nature
# 消息输入框
prompt = st.chat_input("请输入您要问的问题")
if prompt:
st.chat_message("user").write(prompt)
st.session_state.messages.append({"role": "user", "content": prompt})
# 调用AI大模型(流式)
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": system_prompt % (st.session_state.nick_name, st.session_state.nature)},
*st.session_state.messages
],
stream=True
)
response_message = st.empty()
full_response = ""
for chunk in response:
if chunk.choices[0].delta.content is not None:
content = chunk.choices[0].delta.content
full_response += content
response_message.chat_message("assistant").write(full_response)
st.session_state.messages.append({"role": "assistant", "content": full_response})
# 保存会话信息
save_session()

本章总结#

知识块内容
Streamlit 构建页面页面配置、常用组件、侧边栏、列布局、session_state
文件基本操作open / read / write / close、with 上下文管理器
大模型交互方案openai 库 + DeepSeek API、流式输出
大模型会话记忆方案messages 滚雪球
大模型部署方案本地部署 / 官方开放 API / 云服务平台
HTTP 协议请求与响应的数据格式
json 操作dump / load 序列化与反序列化
os / datetime 模块目录与文件判断、会话标识生成
伴侣性格定制system 提示词模板 + %s 占位符

相关#

练习题#

一、回忆填空(写完再展开对答案)#

  1. 五个核心函数:generate_session_name()____()____()____()____()(保存 / 列出 / 加载 / 删除)
  2. 每个会话文件里保存四样东西:messages、____
  3. 保存会话:json.____(session_data, f, ensure_ascii=False, indent=2)
  4. 存盘前先判断并创建目录:if not os.path.____("sessions"): os.____("sessions")
  5. 列出历史会话:os.____("sessions"),再用 filename.____(".json") 筛出会话文件
  6. 让当前会话的按钮高亮:type="primary" if session == st.session_state.____ else "secondary"
  7. 循环里给每个按钮加 key=f"load_{session}",是为了避免 ____ 报错
  8. 数据变了要立刻刷新页面:st.____()
  9. 删除的正好是当前会话时,还要把 st.session_state.messages 清空并把 current_session 换成 ____
填空答案(做完再点开)
  1. save_session、load_sessions、load_session、delete_session 2. nick_namenaturecurrent_session 3. dump 4. exists / mkdir 5. listdir / endswith 6. current_session 7. DuplicateWidgetID(key 重复) 8. rerun 9. 新的会话标识

二、裸写题#

  • 2-1 保存会话save_session():把 messages、昵称、性格、会话标识打包成字典,存进 sessions/<会话标识>.json(目录不存在先创建,中文不转义、有缩进)。

    提示(先自己想,实在想不出再点开)

    一级 · 思路:拼字典 → 确保目录存在 → 用 json 写文件 二级 · 方法os.path.exists / os.mkdir / json.dump / with open(...) 三级 · 骨架:文件名用 f"sessions/{st.session_state.current_session}.json"

    参考答案(做完再点开)
    import os
    import json
    import streamlit as st
    from datetime import datetime
    def save_session():
    if st.session_state.current_session:
    # 拼出要保存的四样东西
    session_data = {
    "nick_name": st.session_state.nick_name,
    "nature": st.session_state.nature,
    "current_session": st.session_state.current_session,
    "messages": st.session_state.messages,
    }
    # 目录不存在就创建
    if not os.path.exists("sessions"):
    os.mkdir("sessions")
    # 写盘:中文不转义、带缩进
    with open(f"sessions/{st.session_state.current_session}.json", "w", encoding="utf-8") as f:
    json.dump(session_data, f, ensure_ascii=False, indent=2)
  • 2-2 加载历史会话load_sessions()(返回所有会话标识列表,按倒序)和 load_session(name)(把读到的数据写回 st.session_state),并用 try/except 包住、失败时提示。

    提示

    一级 · 思路:列目录得到”有哪些”,读单个文件得到”内容是什么” 二级 · 方法os.listdir / json.load / session_list.sort(reverse=True) 三级 · 骨架st.session_state.messages = session_data["____"]

    参考答案(做完再点开)
    import os
    import json
    import streamlit as st
    def load_sessions():
    """返回所有会话标识,倒序(最新的在最前)"""
    session_list = []
    if os.path.exists("sessions"):
    for filename in os.listdir("sessions"):
    if filename.endswith(".json"):
    session_list.append(filename[:-5])
    session_list.sort(reverse=True)
    return session_list
    def load_session(session_name):
    try:
    if os.path.exists(f"sessions/{session_name}.json"):
    with open(f"sessions/{session_name}.json", "r", encoding="utf-8") as f:
    session_data = json.load(f)
    st.session_state.messages = session_data["messages"]
    st.session_state.nick_name = session_data["nick_name"]
    st.session_state.nature = session_data["nature"]
    st.session_state.current_session = session_name
    except Exception:
    st.error("加载会话失败!")
  • 2-3 删除会话(含边界情况)delete_session(name):删掉对应文件;如果删的正好是当前会话,还要清空消息列表并生成新的会话标识。

    提示

    一级 · 思路:先删文件,再判断”删的是不是当前会话” 二级 · 方法os.remove / 条件判断 / generate_session_name() 三级 · 骨架if session_name == st.session_state.____:

    参考答案(做完再点开)
    import os
    import streamlit as st
    from datetime import datetime
    def generate_session_name():
    return datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    def delete_session(session_name):
    try:
    if os.path.exists(f"sessions/{session_name}.json"):
    os.remove(f"sessions/{session_name}.json")
    # 删的正好是当前会话:清空消息 + 换一个新标识
    if session_name == st.session_state.current_session:
    st.session_state.messages = []
    st.session_state.current_session = generate_session_name()
    except Exception:
    st.error("删除会话失败!")

评论区

[ 标签 ]
# AI37# AI 编程2# AI工具1# Ajax2# Apifox1# AstrBot3# Astro2# CC Switch1# CDN2# Claude Code1# claudecode2# ClaudeCode1# Cloudflare2# CloudFlare2# CloudFlare-ImgBed3# coc3# CSS6# DeepSeek6# deepseek2# DELETE1# Docker1# EdgeOne3# Gist1# git1# GitHub1# hexo-circle-of-friends1# HTML6# HTTP5# ImageManager1# Java23# java13# JavaScript5# JDBC3# JSON2# JUnit1# LangChain25# Logback1# Maven6# Muse Spark1# Mybatis1# MyBatis4# MySQL28# MySql1# NapCat1# Node.js1# obsidian2# Obsidian5# OpenCode4# ORM1# PathVariable1# PicGo1# PyCharm1# Python65# RequestBody1# RequestMapping1# RESTful风格1# skills1# Slf4j1# SpringBoot11# SQL2# Streamlit5# Svelte2# TailwindCSS1# Telegram3# Tlias2# Vercel1# vscode2# Vue7# Waline3# WebDAV1# Web基础6# Web开发6# WinSCP1# YAML1# 三层架构1# 中二宣言1# 书籍1# 使用文档10# 写作1# 函数2# 刷步数1# 前端32# 动态1# 动漫1# 包1# 单词2# 博客7# 博客工作流1# 博客开发2# 参数接收1# 友链1# 反思2# 图床6# 地图1# 备份2# 大模型1# 奇思妙想1# 存储1# 学习方法6# 学校1# 宝塔面板3# 宝宝10# 对象1# 导航栏1# 工具2# 开发1# 开发工具1# 开发规范1# 开心1# 异常处理1# 影视2# 微信1# 性能优化2# 总结1# 想法15# 感受1# 感悟11# 指南1# 提示词工程1# 插件5# 故障排除1# 效率工具2# 教程10# 数据分析9# 数据库27# 数据结构1# 文件操作2# 斩神1# 日常92# 日志框架1# 朋友圈1# 朱元璋1# 模块1# 模板1# 正则表达式2# 测试1# 游戏2# 爬虫7# 生活迁移1# 电影2# 电脑1# 碎碎念1# 视觉识别1# 类1# 类型注解1# 网络基础2# 网络教室1# 羊毛2# 脚本2# 脚本工具1# 自动化2# 蓝奏云1# 订阅推荐2# 记录2# 评论系统1# 词根1# 词缀1# 说说1# 足迹1# 跑步2# 路径参数1# 转载2# 运动1# 部落冲突1# 配置1# 随机图1# 面向对象5# 音乐3# 音标1# 饮食1# 驼峰命名1# 高德地图1
[ 公告 ]

如果你喜欢,那么欢迎来到我的世界!

了解更多
[ 音乐 ]
封面

音乐

暂未播放

0:000:00
暂无歌词
找不到相关结果。
[ contents ]
[ 全部文章 ]