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 秒就开始出字)

为什么要流式?三个真实理由

  1. 体验:用户看到字在动,心理等待感大幅下降(哪怕总时间一样)。
  2. 长输出防超时:一次性等 2000 字可能触发网络超时;流式持续有数据,连接不断。
  3. 可中断:用户看了开头觉得跑偏,可以马上 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 条要求:

  1. ✅ while 循环反复读取用户输入
  2. ✅ 带 system prompt 调用 LLM
  3. ✅ 流式打印回复
  4. ✅ 每轮把对话存进 chat_history.json
  5. ✅ 下次启动自动加载历史,接着聊

附加:/clear 清空记忆、/history 查看历史、Ctrl+C 优雅退出(不丢数据)。

没有 API key 也能跑:程序内置 offline 假模型(延续 Day 6 思路),
先把"持久化 + 循环 + 流式打印"的骨架跑通,有 key 时一行切换成真模型。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐