🐎 第8章 器以载道:Agent SDK 智能体开发套件
📄 一句话概括

一次 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 秒实验

  1. 把分叉点从第二步挪到第一步,重跑一遍。两个会话的 token 合计变了多少? 这说明「复制量」跟什么有关?
  2. 在分叉之后,让主线再走五步,重跑一遍。分支受影响吗?如果受影响,说明你的实现是共享而不是复制。
  3. 给会话加上「七天过期」,重跑一遍。过期之后续会话会怎样?这种情况该报错还是该悄悄新建?

常见误区

  1. 以为再调一次就是接着聊。不带标识的调用从零开始,而且不报错。
  2. 用分叉来省成本。它是复制,token 会多出一份。
  3. 会话标识只存在内存里。进程一重启,「上一次」就没了。
  4. 不设过期。每个请求建一个会话,存储很快就满。
  5. 分叉之后还指望两边共享后续改动。分叉就是两条独立的路。
自测三问(点开对答案) 1. 会话的三件事是什么? —— 创建(拿标识)、延续(带标识再调,历史接上)、分叉(复制一份,两条线独立)。 2. 分叉为什么不能省成本? —— 因为它是复制不是共享:分叉点之前的历史两边各存一份,合计反而更多。 3. 续会话最容易踩的坑是什么? —— 以为「再调一次」就是接着聊。不带标识的调用从零开始,而且不报错。

会话会管了,下一节往外扩:让模型能调你自己写的函数。

相关笔记