一次 query 是一轮交互。要跨轮记住东西,靠的是会话标识;要从某个状态试两条路,靠的是分叉。 实测主线三步、从第二步分叉,两边前两步一样、后面完全不同。 分叉是复制,不是共享——它不省成本。
上一节讲怎么配选项,这一节讲怎么把多次调用串成一段对话。 这一节和 7.7 节讲的是同一件事,只是从 SDK 的角度看:会话是对象,不是命令行参数。
一、会话的三件事
创建:第一次调用返回一个标识。存下来,后面都要用。 延续:带着标识再调一次,它把历史接上。 分叉:从某个标识复制出新的,原线不动、新线独立。
三件事之外还有一件隐含的:过期。会话是状态,状态就要考虑什么时候扔掉。
二、真跑:主线三步,第二步分叉
演示里主线走三步:读文件、改入口函数、补测试。在第二步之后分叉。 分叉出来的那条走另一条路:换库、删旧实现,两步。 结果是两个会话的前两步一样,后面完全不同——互不污染。
这就是分叉的用途:同一个开头,试两种走法。
三、分叉是复制,不是共享
分叉点之前那几步,两边各存了一份。所以两个会话的 token 相加,会比主线多出分叉点那部分。 演示里主线累积二十三个 token、分支三十五个,合计五十八——多出来的就是那份复制。 所以分叉适合「试两条路」,不适合「省成本」。想省成本该做的是压缩历史(7.7 节那个「传结论」)。
这个区别在实现上也很明显:分叉要真的复制一份状态,共享则要处理「一边改了另一边看得见」的问题。
四、三个实操点
会话标识必须自己存。程序重启后它不会记得「上一次」。 分叉前先确认:要复用的是「上下文」还是「结论」?后者用摘要更省。 会话是状态,要考虑过期。留一堆没人用的会话,只会在存储里长草。
第三条在服务化之后尤其重要:每个请求都建一个会话,一天就是几万个。
五、一个容易踩的坑
以为「再调一次」就是接着聊。不带标识的调用都是从零开始。 而这个坑不会报错:它只是表现得像「忘了前面说过什么」。 所以调试这类问题时,第一件事是打印会话标识——确认这次带的是不是你以为的那个。
这和 7.7 节那条「会话标识要显式存下来」是同一件事,只是那边是命令行,这边是代码。
六、动手 Demo:主线走三步,从第二步分叉,两边各走各的
一次 query 是一轮交互。要跨轮记住东西,靠的是会话标识;会话还能分叉。 这里真跑:主线走三步,第二步之后分叉,两条线各走自己的路。
a = st.create()
st.say(a, u"读 src/app.py")
st.say(a, u"把入口函数改短")
b = st.fork(a) # 从这一步复制出一份
st.say(a, u"补一个测试")
st.say(b, u"改用另一个库")
实跑输出:
--------------------------------------------------------------------
主线走三步,从第二步分叉,两边各走各的
--------------------------------------------------------------------
一次 query 是一轮交互。要跨轮记住东西,靠的是会话标识。
会话还能分叉:从某个状态复制一份,两条线互不影响。
① 会话的三件事
--------------------------------------------------------------------
| 动作 | 怎么做 | 注意什么 |
|---|---|---|
| 创建 | 第一次调用返回一个标识 | 存下来,后面都要用 |
| 延续 | 带着标识再调一次 | 它把历史接上 |
| 分叉 | 从某个标识复制出新的 | 原线不动,新线独立 |
② 真跑:主线三步,第二步分叉
--------------------------------------------------------------------
| 会话 | 几步 | 各自的记录 |
|---|:---:|:---:|
| s-01 | 3 | 读 src/app.py / 把入口函数改短 / 补一个测试 |
| s-02 | 4 | 读 src/app.py / 把入口函数改短 / 改用另一个库 / 再删掉旧实现 |
两个会话的前两步一样(分叉点之前),后面完全不同。
这就是分叉的用途:同一个开头,试两种走法,互不污染。
③ 上下文成本怎么看
--------------------------------------------------------------------
主线累积 23 token,分支累积 35 token,合计 58。
注意分叉点之前那两步,两边各存了一份——分叉不是共享,是复制。
所以分叉适合「试两条路」,不适合「省成本」。想省成本该做的是压缩历史。
④ 三个实操点
--------------------------------------------------------------------
- 会话标识必须自己存。程序重启后它不会记得「上一次」。
- 分叉前先确认:要复用的是「上下文」还是「结论」?后者用摘要更省。
- 会话是状态,要考虑过期。留一堆没人用的会话,只会在存储里长草。
⑤ 一个容易踩的坑
--------------------------------------------------------------------
以为「再调一次」就是接着聊。不带标识的调用都是从零开始。
而这个坑不会报错:它只是表现得像「忘了前面说过什么」。
! 这里的会话存储是简化的内存实现,真实实现把状态放在可恢复的地方。
①会话三件事:创建(拿标识,存下来)、延续(带标识再调,历史接上)、分叉(复制一份,原线不动)。 外加一件隐含的:过期。 ②主线三步、从第二步分叉,分出来那条走两步。两个会话的前两步一样,后面完全不同,互不污染。 ③分叉是复制不是共享:分叉点之前那几步两边各存一份,所以两个会话的 token 相加会多出一份。 它适合试两条路,不适合省成本。 ④最常踩的坑:以为「再调一次」就是接着聊。 不带标识的调用都是从零开始,而且它不报错,只表现得像「忘了前面说过什么」。
30 秒实验
- 把分叉点从第二步挪到第一步,重跑一遍。两个会话的 token 合计变了多少? 这说明「复制量」跟什么有关?
- 在分叉之后,让主线再走五步,重跑一遍。分支受影响吗?如果受影响,说明你的实现是共享而不是复制。
- 给会话加上「七天过期」,重跑一遍。过期之后续会话会怎样?这种情况该报错还是该悄悄新建?
常见误区
- 以为再调一次就是接着聊。不带标识的调用从零开始,而且不报错。
- 用分叉来省成本。它是复制,token 会多出一份。
- 会话标识只存在内存里。进程一重启,「上一次」就没了。
- 不设过期。每个请求建一个会话,存储很快就满。
- 分叉之后还指望两边共享后续改动。分叉就是两条独立的路。
自测三问(点开对答案)
1. 会话的三件事是什么? —— 创建(拿标识)、延续(带标识再调,历史接上)、分叉(复制一份,两条线独立)。 2. 分叉为什么不能省成本? —— 因为它是复制不是共享:分叉点之前的历史两边各存一份,合计反而更多。 3. 续会话最容易踩的坑是什么? —— 以为「再调一次」就是接着聊。不带标识的调用从零开始,而且不报错。会话会管了,下一节往外扩:让模型能调你自己写的函数。
- ☐ (本篇完,下一篇 8.6 自定义工具:扩展 Claude 的能力边界)
相关笔记
- 会话管理:流水线里的会话成本
- 自定义工具:自定义工具
- Claude Code 橙皮书 · 索引
- 🐎 Claude Code 实战 · 索引