Python全栈入门到实战【AI实战篇 07】AI应用会话记忆功能全实现,第一个AI项目连续对话核心
前言上一篇《AI实战篇 06》中我们已经完成了基于Streamlit的AI聊天消息展示模块开发解决了Streamlit无状态特性导致的历史消息被覆盖问题实现了聊天记录的持久化展示。但此时的应用仅能被动展示历史消息不具备真正的会话记忆能力——AI无法关联连续提问的上下文导致追问类场景的回复完全偏离预期。本篇作为第一个AI项目的第七篇我们将从问题发现、本质分析、思路梳理到代码落地一步步带你彻底实现AI应用的会话记忆功能这是所有对话类AI应用的核心体验基础。本文为Python全栈开发者与零基础AI入门者量身打造全程遵循“发现问题→分析问题→解决问题→验证效果”的完整开发逻辑仅需修改一行核心代码即可实现功能同时深入讲解底层原理和关键注意事项即使是完全没有AI开发经验的同学也能理解会话记忆的本质并成功实现连续对话功能。本节核心学习内容问题复现直观展示AI无法理解连续追问的异常现象本质解析深入理解大模型API无状态设计的核心特性思路梳理从问题定位到技术选型的完整解决思路推导代码落地核心原理讲解、一行代码修改实现会话记忆、完整最终代码功能测试多场景验证会话记忆效果、用户隔离验证关键注意事项上下文长度限制、消息格式、系统提示词位置等核心避坑点核心总结会话记忆实现核心速查表方便开发时快速查阅文章目录前言一、问题发现与本质分析1.1 问题场景复现1.2 问题本质深度解析大模型API的无状态特性此前代码的缺陷二、核心解决思路梳理2.1 问题定位思路2.2 原理推导思路2.3 技术选型思路2.4 代码实现思路2.5 验证思路三、解决方案与代码落地3.1 核心原理存储-传递-更新三步法3.2 核心代码修改仅需修改一行修改前的代码修改后的代码3.3 核心代码逐行解析3.4 完整最终代码四、功能测试与效果验证4.1 基础测试步骤4.2 测试结果4.3 进一步验证五、关键注意事项六、核心总结会话记忆实现核心速查表七、专栏订阅一、问题发现与本质分析在完成历史消息展示功能后我们进行连续对话测试发现了一个严重影响用户体验的核心问题应用仅能被动展示历史聊天消息但AI无法关联连续提问的上下文导致追问类场景的回复完全偏离预期。1.1 问题场景复现我们通过最典型的“分苹果”连续追问场景稳定复现这个问题第一轮提问在聊天输入框中输入“现在有12个苹果4个人怎么均分苹果”点击发送AI正常回复“12个苹果分给4个人每人可以分到3个苹果”第二轮追问紧接着输入“那两个人呢”点击发送AI异常回复无法理解“那两个人呢”的指代含义会回答“两个人可以一起学习、一起工作”等完全无关的内容1.2 问题本质深度解析很多人会误以为这是AI模型的能力缺陷但实际上这是所有大模型API的通用设计特性并非bug。我们通过排查代码和API调用日志最终定位到问题的根源大模型API的无状态特性所有大模型API都采用“单次请求-单次响应”的无状态设计服务器不会为任何用户存储历史会话信息模型的所有输出都100%依赖本次请求中传入的messages参数此前代码的缺陷此前的代码中调用DeepSeek API时的messages参数是这样的messages[{role:system,content:system_prompt},{role:user,content:prompt},]可以清晰地看到这里只传入了当前用户的单次提问没有携带任何历史会话信息。因此模型根本不知道用户之前问过“分苹果”的问题自然无法理解“那两个人呢”的指代含义。二、核心解决思路梳理基于上述问题分析我们梳理出了一套完整的解决思路从问题定位到最终验证环环相扣2.1 问题定位思路先通过最小可复现场景分苹果连续追问稳定复现问题排除基础错误检查代码语法、API密钥、网络连接、模型名称是否正确抓包查看API请求参数对比正常请求和异常请求的差异发现核心差异异常请求的messages参数中缺少历史会话信息得出结论问题根源是API调用时未传递上下文而非模型能力不足2.2 原理推导思路既然大模型API本身不存储任何会话那么要实现会话记忆只能由客户端主动存储并传递上下文模型只能识别[{role: xxx, content: xxx}]格式的消息列表因此历史消息也必须遵循这个格式每次调用API时需要将“系统提示词 所有历史用户提问 所有历史AI回复”一起传递给模型这样模型就能看到完整的对话过程从而理解连续提问的上下文关联2.3 技术选型思路我们需要一个轻量、会话级、支持用户隔离的存储容器来保存历史消息逐一排除不合适的方案❌ 普通变量Streamlit每次交互都会重新运行整个脚本普通变量会被重新初始化无法保留数据❌ 本地文件多用户场景下会出现文件读写冲突且无法实现用户隔离❌ 数据库对于轻量应用来说过于复杂增加开发和部署成本✅Streamlit原生st.session_state会话级别的字典存储生命周期与用户浏览器会话一致天然支持用户隔离无需额外配置是最优方案2.4 代码实现思路初始化阶段在st.session_state中创建一个空列表messages用于存储所有历史聊天消息页面渲染阶段遍历messages列表按角色用户/助手渲染所有历史消息用户输入阶段捕获用户的新提问先渲染到页面再追加到messages列表API调用阶段将系统提示词与messages列表中的所有历史消息拼接作为messages参数传入API结果处理阶段提取AI的回复渲染到页面再追加到messages列表为下一次提问准备上下文2.5 验证思路再次执行“分苹果”的连续追问场景验证AI能否正确关联上下文多轮连续提问验证会话记忆是否能持续生效刷新浏览器页面验证历史消息是否仍然保留打开多个浏览器标签页验证不同用户的会话是否相互隔离三、解决方案与代码落地3.1 核心原理存储-传递-更新三步法实现会话记忆的核心逻辑可以总结为“存储-传递-更新”三步法存储用st.session_state.messages存储所有历史聊天消息格式为[{role: xxx, content: xxx}]传递调用API时将系统提示词与所有历史消息一起传递给模型更新每次用户提问、AI回复后都将新内容追加到st.session_state.messages中形成上下文闭环3.2 核心代码修改仅需修改一行对比历史消息展示版本的代码我们只需要修改API调用时的messages参数这一行就能实现完整的会话记忆功能。修改前的代码# 调用OpenAI APIresponseclient.chat.completions.create(modeldeepseek-chat,messages[{role:system,content:system_prompt},{role:user,content:prompt},],streamFalse)修改后的代码# 调用OpenAI API核心修改传入全量历史消息responseclient.chat.completions.create(modeldeepseek-chat,messages[{role:system,content:system_prompt},*st.session_state.messages,# 解包session_state中的所有历史消息],streamFalse)3.3 核心代码逐行解析*st.session_state.messages这是Python中的解包语法作用是将st.session_state.messages列表中的每一个元素都作为独立的元素传入messages数组中。举个具体的例子如果st.session_state.messages中有两条历史消息st.session_state.messages[{role:user,content:现在有12个苹果4个人怎么均分苹果},{role:assistant,content:12个苹果分给4个人每人可以分到3个苹果}]那么*st.session_state.messages解包后最终传递给API的messages参数就变成了messages[{role:system,content:我是一名Python编程老师我的名字叫做小辉辉有10年的教学经验},{role:user,content:现在有12个苹果4个人怎么均分苹果},{role:assistant,content:12个苹果分给4个人每人可以分到3个苹果}]这样模型就能完整看到之前的对话过程当用户再问“那两个人呢”时模型就知道是在问“12个苹果分给2个人怎么分”。3.4 完整最终代码将ai_chat.py中的代码全部替换为以下内容importstreamlitasstimportosfromopenaiimportOpenAI# 设置页面的配置项st.set_page_config(page_title小辉AI,# 标题page_iconimgs/logo.png,# 图标layoutwide,# 布局占页面全部initial_sidebar_stateexpanded,# 侧边栏状态menu_items{})# 页面的大标题st.title(小辉AI)# 设置logo图片st.logo(imgs/logo.png,sizelarge)# 创建OpenAI客户端clientOpenAI(api_keyos.environ.get(DEEPSEEK_API_KEY),base_urlhttps://api.deepseek.com)# 初始化聊天信息ifmessagesnotinst.session_state:st.session_state.messages[]# 创建一个列表用于存储聊天信息, 默认值为空# 定义系统提示词system_prompt我是一名Python编程老师我的名字叫做小辉辉有10年的教学经验# 将聊天信息显示在页面上formessageinst.session_state.messages:ifmessage[role]user:st.chat_message(user).write(message[content])else:st.chat_message(assistant).write(message[content])# 消息输入框promptst.chat_input(请输入您的问题)# prompt获取到用户输入的提示词ifprompt:st.chat_message(user).write(prompt)# 将用户输入的提示词添加到session_state.messages中st.session_state.messages.append({role:user,content:prompt})# 调用OpenAI API核心修改传入全量历史消息responseclient.chat.completions.create(modeldeepseek-chat,messages[{role:system,content:system_prompt},*st.session_state.messages,# 获取session_state.messages中的所有消息],streamFalse)st.chat_message(assistant).write(response.choices[0].message.content)print(response.choices[0].message.content)# 将OpenAI API返回的答案添加到session_state.messages中st.session_state.messages.append({role:assistant,content:response.choices[0].message.content})四、功能测试与效果验证4.1 基础测试步骤按CtrlC终止之前的Streamlit服务重新执行启动命令streamlit run .\ai_chat.py第一轮提问输入“现在有12个苹果4个人怎么均分苹果”点击发送等待AI回复完成后第二轮追问输入“那两个人呢”点击发送4.2 测试结果AI成功关联上一轮的上下文返回了完全符合预期的结果第一轮回复“12个苹果分给4个人每人可以分到3个苹果”第二轮回复“12个苹果分给2个人的话每人可以分到6个苹果”4.3 进一步验证继续第三轮追问“那三个人呢”AI会正确回答“每人分4个苹果”刷新浏览器页面所有历史消息仍然保留在页面上打开一个新的浏览器标签页访问相同地址会得到一个全新的空白会话验证了不同用户的会话相互隔离五、关键注意事项上下文长度限制DeepSeek-chat模型的上下文窗口为128k token当会话消息过多时会超出token限制导致API调用失败。此时可以通过截断历史消息解决例如只保留最近的20条消息# 在调用API前添加这行代码st.session_state.messagesst.session_state.messages[-20:]消息格式正确性st.session_state.messages中的每一条消息都必须严格遵循{role: xxx, content: xxx}的格式不能有其他格式的元素否则会导致API调用报错。系统提示词位置系统提示词必须放在messages数组的第一个位置不能放在中间或末尾否则模型可能无法正确识别系统提示词。会话生命周期st.session_state的生命周期与浏览器标签页一致关闭标签页后会话会自动清除。如果需要跨会话记忆需要结合数据库实现消息持久化。六、核心总结会话记忆实现核心速查表为了方便后续开发时快速查阅整理了会话记忆实现的核心速查表核心环节关键操作核心要点问题本质大模型API无状态不存储历史会话所有输出仅依赖本次传入的messages参数核心原理存储-传递-更新三步法客户端主动存储并传递完整历史上下文存储方案st.session_state.messages会话级存储天然支持用户隔离核心代码修改解包全量历史消息传入API*st.session_state.messages消息格式{“role”: “user/assistant”, “content”: “xxx”}必须严格遵循否则API调用失败系统提示词必须放在messages数组的第一个位置不能放在中间或末尾长度控制截断早期非关键对话避免超出模型的token上限用户隔离不同用户的st.session_state相互独立无需额外配置自动实现七、专栏订阅专栏优点《Python从入门到实战》专栏内容涵盖Python基础到高级编程、并发编程进程/线程/协程、网络编程TCP/UDP/Socket、核心内置/第三方模块、数据库核心实战、Web开发Django/Flask/FastAPI框架、数据库MySQL/ORM/异步数据库、网络爬虫同步/异步/分布式、AI实战、Linux部署运维等全栈核心知识以项目驱动教学构建清晰学习路径适合零基础入门和进阶提升的同学跟着一步步从入门到精通专栏地址https://blog.csdn.net/zsh_1314520/category_13108073.html文章是永久吗一次订阅后可永久免费查看专栏内所有文章后续会持续更新全栈相关内容第一时间获取最新教程有答疑交流群吗订阅专栏后有专属的全栈学习答疑群群内提供专业问题答疑、和众多学习者抱团取暖一起沉淀技术、赋能成长进群方式订阅专栏后可直接在专栏内申请加入答疑群或私信博主沟通进群事宜https://bbs.csdn.net/topics/620104702更多干货点赞收藏关注博主不迷路博主博客链接https://blog.csdn.net/zsh_1314520?spm1000.2115.3001.5343专注Python全栈技术分享评论区留言问题会一一回复助力大家轻松搞定Python全栈【原创声明】除本文原文地址以外如发现同款内容皆为盗版本文已收录于《Python全栈从入门到实战》请勿购买盗版文章和专栏如购买盗版内容不提供任何服务。原文地址https://blog.csdn.net/zsh_1314520/article/details/163779577

相关新闻

python的运筹学工业场景模拟第二十九篇:冷链仓储调拨,仓库温度容量双重约束,运输成本差异化,求解最优货物调拨方案。

python的运筹学工业场景模拟第二十九篇:冷链仓储调拨,仓库温度容量双重约束,运输成本差异化,求解最优货物调拨方案。

冷链仓储调拨优化:用 Python PuLP 破解温区与成本困局"某生鲜电商的区域冷链仓,下辖3个前置仓(冷冻-18℃、冷藏4℃、常温15℃),每天要从中心仓调拨生鲜商品。中心仓空间紧张,前置仓各有温区容量上限。…

2026/10/29 2:33:55 阅读更多 →
迅雷不限速下载的方法,获取直连,提高百倍下载速度

迅雷不限速下载的方法,获取直连,提高百倍下载速度

当我们不想开会员再迅雷下载文件的时候,而且希望下载速度比肩会员的时候,不妨来看看我这方法:点我打开方法 下载速度是不受限制,主要看你的网速和宽带跑个10几M/秒不是问题,亲测有效,接下来就是教程部分 打…

2026/10/29 9:17:03 阅读更多 →
AI写论文哪个软件最好?答案可能和你想的完全不一样

AI写论文哪个软件最好?答案可能和你想的完全不一样

“AI写论文哪个软件最好?” 每次直播,弹幕里总有人刷这个问题。我一般会反问一句:“你问的是‘哪个能帮你写’,还是‘哪个敢让你查’?” 这两个问题的答案,天差地别。 先泼一盆冷水:通用AI的…

2026/10/29 9:17:01 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/30 0:01:36 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/30 9:20:34 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/30 10:19:21 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/30 0:01:37 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/30 10:19:39 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/30 9:21:37 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/29 17:01:10 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/30 10:28:40 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/30 0:01:33 阅读更多 →