For the complete documentation index, see llms.txt. This page is also available as Markdown.

第 4 课:Session 持久化——Transcript、Checkpoint 与 Memory

第 3 课已经把排查过程写进了 session.jsonl。现在增加一个需求:关闭程序,明天还能接着查同一个问题。

文件还在,只解决了记录没有丢。程序还要知道打开哪份记录、已经进行到哪一步,以及本次应该把什么交给模型。上一课的临时目录用于观察结果,长期续跑还需要稳定的存储位置。存了聊天记录,不等于接上了昨天的话。

1. Session:会话身份与存储边界

把同一段连续交互归在一起,这个单位叫 Session(会话)。应用可以给它一个编号,例如 session_id="order-debug-01"。第三课从用户提问到最终回答是一个 Turn;围绕这次故障继续追问,可以形成同一个 Session 中的多个 Turn。

一种本地存储布局可以是:

sessions/
|-- order-debug-01/
|   |-- session.jsonl
|   +-- checkpoint.json
+-- order-debug-02/
    |-- session.jsonl
    +-- checkpoint.json

选择 order-debug-01,程序就去找这一组记录;开启新会话,则分配另一个编号。编号负责关联数据,不会让模型自动记住内容,也不代表访问授权。

这是布局示意,不是上一课 demo 已有的续跑入口。Session 是逻辑归属,可以落在目录、数据库记录里,也可以对接模型服务托管的对话状态。[1] 文件扩展名不是它的定义。

2. Transcript 与 Checkpoint:历史记录和恢复状态

Transcript 记录这段会话按顺序发生过什么,包括用户消息、模型调用申请和工具结果。Checkpoint 保存某个位置的状态,帮助程序从那里继续。

先用三笔流水看区别:存入 10 元,支出 3 元,再存入 5 元。完整流水能算出余额 12 元;如果已经保存了“处理完前两笔,余额 7 元”,恢复时只需再处理第三笔。

两次输出都是 12。processed=2 表示前两笔已经计入,切片从第三笔开始。快照中的状态必须和它标记的位置对应,否则会重复计算或漏掉事件。

这里是把已记录的第三笔算进余额,不是再存一次钱。同样,回放 Transcript 是重建应用状态,不代表把历史工具调用重新执行一遍。

完整流水保留,快照记录前两笔的余额 7,小黑只处理第三笔加 5,恢复为 12

快照已计入前两笔,恢复从余额 7 接着处理第三笔加 5。完整流水仍保留;这里重建的是状态,不是重做历史动作。

放回排查助手:

概念
要回答的问题
可以保存什么

Transcript

当时问了什么,读到了什么?

完整的消息、调用编号、工具结果与相关事件

Checkpoint

程序最后保存到了哪里?

截至某个位置的消息或摘要、运行进度、恢复所需字段

一份快照可以包含部分历史,也可以包含已经整理好的状态,但它不一定能还原所有原始事件。保存哪些字段,要由程序准备怎样恢复来决定;Checkpoint 不要求复制整个 Python 进程。

3. JSON 与 JSONL:数据格式和文件组织

JSON 表示一份完整的结构化数据,适合保存一份当前快照;JSONL 则是一行一个独立 JSON 值,本书用每行一个对象记录事件,便于顺序追加。

下面用两条手工构造的短消息演示快照存取。它们只用来展示文件内容,不是模型运行实录。

输出为:

这份快照包含会话编号、截至第几条记录、两条消息和当前摘要。processed_entries 对应前面流水例子的 processed,这里计数的是会话记录。先写完临时文件,再在同一文件系统内替换目标,避免在覆盖过程中读到半份 JSON。示例只演示单写者的文件存取;断电持久性、并发写入还需要额外保证。临时目录会被清理,真实应用应使用长期保留的位置。

相同的两条消息,放进 Transcript 时可以逐条追加到 JSONL:

读取时逐行解析,每行都是完整 JSON;不能把整份多行文件当作一个 JSON 对象一次解析。完整 Transcript 也能存成 JSON 数组,这里选 JSONL 是为了方便逐条追加。本例只有两条消息事件;真实 Transcript 还可能记录其他事件,恢复位置必须按所用格式计算。第三课的 Tool Call 与 Tool Result 字段也必须完整保留。

Checkpoint 不一定要单独放文件。它也可以作为一种事件,写进同一个 JSONL:

程序找到这份快照后,再处理对应的后续事件。第 3 行是一条状态记录,不是多发生了一次用户交互。因此,Transcript 与 Checkpoint 描述用途,JSON 与 JSONL 描述数据怎样组织;同一个文件可以承担两种用途。

4. Memory 与 Context:跨会话信息和当次输入

继续 order-debug-01,需要知道上次停在“等待日志位置”。开启一个全新的会话,却不应该无缘无故继承这项进度。

有些信息适用得更久,例如用户确认的“默认使用中文”,或项目约定“诊断要注明文件位置”。这些经过选择、允许跨会话使用的信息,可以保存为 Memory(长期记忆)。

信息
使用范围

当前排查等待哪份材料

当前 Session

本项目的诊断报告约定

同一项目的多个 Session

用户默认使用哪种语言

适用该偏好的不同项目与 Session

沿用上一节读回的 restored,下面只比较本次选中的材料,不调用模型:

输出为:

同一份长期信息可以被再次选用,旧进度却只跟随对应会话。代码中的字符串列表只是材料清单;实际请求还要按模型协议组织指令、历史消息和工具说明,不能拿它替代完整的消息列表。

写到文件里,叫持久化;经过应用选择、实际放进这次模型请求的材料,才构成这次的 Context(上下文)。磁盘上保存了多少,不等于模型本次看到了多少。

Memory 不应照抄所有历史。临时日志、未确认的猜测和凭据,不应自动成为长期事实;已保存内容也要能更新或删除。用户本次要求英文,可以覆盖默认中文偏好;强制安全策略不能被新偏好覆盖,更不能靠记忆里一句“已经批准”获得权限。

5. 恢复边界与存储演进

快照只证明程序保存过某个状态,不证明外部世界仍停在那里。以后允许助手修改文件时,可能出现:

此时按旧快照重新执行,可能重复操作或覆盖用户后来的修改。第 6 课会用工具执行账本 Ledger 核对执行结果。只读工具也要分清:重新读取的是当前文件,不一定是当时的那份内容。

Checkpoint 可以在完整 Turn 结束后保存,也可以支持中途恢复,前提是保存了对应的现场。这里的文件读回还不是一次跨进程 Agent 续跑验证,不能把它当成完整故障恢复系统。

存储规模增长以后,再根据读写方式决定是否需要数据库。低频顺序追加与回放可以使用 JSONL;频繁查询、需要事务和唯一约束时,可以考虑 SQLite 等数据库。[2] 不必为了“到了十万行”就迁移,也不必等到十万行才处理并发问题。相关实现留在第 6 课和存储专项练习。

会话现在有了归属,历史与恢复点也能保存。下一课处理另一个问题:记录越积越多,哪些内容应该进入有限的模型窗口?

本课实践

进入第 4 课实践:保存并读回会话,增加一条消息,核对历史与快照位置。说明中列出必做步骤、完成标准和选做内容。

资料

Last updated

Was this helpful?