Agent开发——Day 07持久化对话历史 + 流式输出
Day 07 案例分析:持久化对话历史 + 流式输出
对应学习计划 Week 2 的「动手任务 / 完成标志」:手写一个带持久化对话历史的命令行聊天程序。
今天不是学新概念,而是把 Day 6 的概念拼成一个真能跑的程序。
今天要拼起来的「四个零件」
| 零件 | 来自哪天 | 在今天程序里干什么 |
|---|---|---|
messages 历史列表 |
Day 6 | 模型的"记忆",每轮 append 两条(user + assistant) |
| JSON 读写 | Day 2 / Day 5 | 把 history 存进文件,下次启动读回来 |
| 流式输出 stream | Day 6 | 回复一个字一个字蹦出来,不用干等 |
| while 循环 + 异常处理 | Day 1-2 | 反复读输入、优雅退出、出错不崩 |
一句话:Day 7 = Day 6 的 messages + Day 5 的 JSON 文件 + 流式打印,缝合成一个聊天器。
案例一:ChatGPT / Claude 网页版的「对话历史」到底是什么?
你看到的现象
左侧边栏一排历史会话,点哪个就接着哪个聊,关掉浏览器明天打开还在。
它的本质(拆穿魔法)
每一个会话 = 一个 messages 列表,存在数据库里。
会话A: [ {user:"帮我写诗"}, {assistant:"床前明月光..."}, {user:"再来一首"}, ... ]
会话B: [ {user:"解释量子纠缠"}, {assistant:"..."} ]
会话C: [ ... ]
- 切换会话 = 换一个 messages 列表加载进来。
- "接着聊"能记住 = 把这个列表完整发给 API(Day 6 的"无状态":模型自己不记,靠程序回传)。
- 关掉还在 = 列表存在了数据库/文件里,不在内存里。
我们今天的程序就是这套机制的最小版:
网页版用数据库存多个会话 → 我们用一个 JSON 文件存一个会话。原理一模一样。
关键洞察
ChatGPT 不是"有记忆的 AI",而是"一个会把历史从磁盘读出来、再原样发给无状态模型的普通程序"。
你今天写的 80 行代码,和 ChatGPT 的记忆机制是同一个原理,只是规模不同。
案例二:为什么 ChatGPT 是「一个字一个字蹦」的?—— 流式输出 (stream)
两种拿结果的方式对比
非流式(普通调用):
你发问 → [模型憋大招,生成全部 500 字] → 一次性甩给你
└─────────── 你干等 8 秒 ───────────┘ 屏幕一直空白
流式 (stream=True):
你发问 → 模型每生成一个 token 就立刻推一个过来 → 你边生成边看
你 → 床 → 前 → 明 → 月 → 光 ...(0.1 秒就开始出字)
为什么要流式?三个真实理由
- 体验:用户看到字在动,心理等待感大幅下降(哪怕总时间一样)。
- 长输出防超时:一次性等 2000 字可能触发网络超时;流式持续有数据,连接不断。
- 可中断:用户看了开头觉得跑偏,可以马上 Ctrl+C 停,不用等它说完。
代码长什么样(Claude SDK)
# 非流式:一次拿全
resp = client.messages.create(model=..., messages=history, max_tokens=512)
print(resp.content[0].text)
# 流式:边收边打印
with client.messages.stream(model=..., messages=history, max_tokens=512) as stream:
for text in stream.text_stream: # 每次拿到一小段文字
print(text, end="", flush=True) # end="" 不换行,flush 立刻显示
full_reply = stream.get_final_message().content[0].text # 收完拿完整文本回填 history
⚠️ 易错点:流式打印时别忘了
flush=True。否则 Python 会"攒着"字符不立刻显示,
打字机效果就没了——你会发现还是憋到最后一次性出来。
案例三:长对话会「失忆」—— 上下文窗口与三种缓解策略
现象(Day 6 已埋伏笔)
客服 Agent 聊到第 80 轮,忘了第 1 轮说的订单号。
原因(一句话)
history 越来越长 → 累计 token 超过上下文窗口上限 → 最早的消息被丢掉 → 忘了开头。
(Haiku 窗口 ≈ 200K token,Opus/Sonnet ≈ 1M)
三档缓解策略(面试常问,记牢这张表)
| 策略 | 做法 | 代价 | 对应学习计划 |
|---|---|---|---|
| 滑动窗口 | 只保留最近 N 轮,旧的直接扔 | 最简单,但会忘开头 | 今天程序里可加 |
| 摘要记忆 | 旧对话压成一段摘要,替换原文 | 省 token,但丢细节 | Week 11 |
| 向量记忆 (RAG) | 历史存向量库,按"语义相关"检索片段 | 最强也最复杂 | Week 11 + 第五阶段 |
今天我们先把**最朴素的"全量存储"**写通(够短的对话不会超窗口)。
等 Week 11 再学怎么"压缩记忆"——你现在只需理解:所有"记忆"本质都是在管理那个 messages 列表。
今日动手目标(day007.py)
写一个命令行聊天器,满足 Week 2 的 5 条要求:
- ✅ while 循环反复读取用户输入
- ✅ 带 system prompt 调用 LLM
- ✅ 流式打印回复
- ✅ 每轮把对话存进
chat_history.json - ✅ 下次启动自动加载历史,接着聊
附加:/clear 清空记忆、/history 查看历史、Ctrl+C 优雅退出(不丢数据)。
没有 API key 也能跑:程序内置 offline 假模型(延续 Day 6 思路),
先把"持久化 + 循环 + 流式打印"的骨架跑通,有 key 时一行切换成真模型。
更多推荐
所有评论(0)