小节四:把 Session 接回 ContextBuilder 和 Agent Engine——真正实现“继续刚才那个任务”

现在我们把前几节串起来。

当前已有:

SessionManager
WorkingMemory
ContextBuilder
Agent Engine

一个新请求进来时,流程应该变成:

请求携带 session_id
  ↓
SessionManager.get_session()
  ↓
取出 messages
working_memory
tool_history
  ↓
ContextBuilder.build()
  ↓
Agent Engine

Agent 执行完以后:

新增 messages
更新 working_memory
追加 tool_history
关联 run_id
  ↓
SessionManager 保存

这就是一个完整闭环。

真正接回 Agent 时,可以把链路理解成下面这样:请求先根据 session_id 找回状态,再由 ContextBuilder 组装上下文,Agent 执行结束后再把新的状态写回同一个 Session。

图片[1]-小节四:把 Session 接回 ContextBuilder 和 Agent Engine——真正实现“继续刚才那个任务”-大赛人网

这个闭环解决的是“下一次请求怎么接着上一次继续”。输入侧恢复 messages / working_memory / tool_history,输出侧再追加新消息、更新 Memory、记录 Tool 和 Run。这样 Session 才不是静态存档,而是整个多轮交互的状态中枢。

新用户第一次请求

如果没有 session_id:

create_session()

返回:

session_id

客户端以后都带这个 ID。

老用户继续请求

请求:

{
  "session_id": "sess_xxx",
  "message": "继续刚才的任务"
}

服务端:

get_session
 ↓
恢复上下文
 ↓
继续 Agent

一个完整内存版 Demo

下面实现一个非常简单的“可恢复对话”。

第二次输出能够直接知道:

当前 Goal
当前 Next Action

这就是 Session + Working Memory 联合的价值。

这段代码还缺什么?

非常多。

例如:

真正持久化
多用户
并发安全
Session 过期
Run Manager
数据库
权限

但架构已经成立。

后面服务化时只要替换底层存储,不需要重写整个 Agent 逻辑。

继续聊天与继续执行,需要不同的信息

用户下午回来问“接着上午的排查继续”,我们可以从 Session 恢复目标和历史。但如果上午的任务停在文件写入中途,仅靠聊天记录无法确认文件是否已经改变。恢复对话需要上下文,恢复执行还需要动作记录、资源状态和检查点,两条链路要分别设计。

另一个常见情况是用户开了两个页面,同时向同一 Session 提交任务。如果两个 Run 都读到旧版本,再各自写回 Memory,后提交的结果可能覆盖前一个。教学实现可以先限制同一 Session 串行执行;需要并行时,再使用版本检查或明确分支,避免默默合并冲突。

Session 的归属、过期和访问权限应在读取与写入时检查。会话编号只是标识,不是授权凭据。长期记忆也不能因为出现在同一个数据库,就被其他用户的 Session 自动检索到。

动手推演:两次写入谁留下来

让两个模拟请求都读取版本 3,第一个更新到版本 4,第二个仍以版本 3 提交。参考结果是第二个被告知冲突,重新读取后再决定如何合并。若最后只剩其中一份内容而没有任何错误,我们就发现了一次丢失更新。

这就是典型的“丢失更新”问题。用版本号把两个请求放到同一条时间线上看,会更直观:

图片[2]-小节四:把 Session 接回 ContextBuilder 和 Agent Engine——真正实现“继续刚才那个任务”-大赛人网

expected_version 的意义不是增加一个字段,而是明确告诉服务端:“我的修改是基于哪个快照算出来的。”如果 Session 已经从 3 变成 4,旧版本写入就应该被拒绝,让客户端重新读取后再决定是否合并,而不是静默覆盖最新状态。

网络重试还会带来另一类重复

并发之外,客户端超时重试也很常见。第一次请求可能已经成功追加消息,只是响应在网络途中丢了;客户端再次提交同一内容,如果服务端没有 request_id 或幂等键,就会得到两条相同消息,甚至启动两个 Run。

一个实用做法是让每次写请求携带唯一 request_id,Session Store 记录已经处理过的 ID。再次收到相同 ID 时返回第一次结果,不重复追加消息或创建 Run。这里不要用消息正文充当幂等键——用户连续发送两句相同的话,完全可能是合法行为。

网络重试和并发冲突不是一回事。这里的问题是“同一个写请求可能被发送两次”,所以更适合用独立的 request_id 做幂等控制:

图片[3]-小节四:把 Session 接回 ContextBuilder 和 Agent Engine——真正实现“继续刚才那个任务”-大赛人网

第一次请求如果其实已经写成功,只是响应丢在网络途中,第二次带着相同 request_id 再来时,服务端应该直接返回第一次结果。这样既不会重复追加同一条消息,也不会因为一次客户端重试额外创建一个 Run。

在配套项目里找到这一节

课程中的可运行实现位于:

src/agent_harness/context/session.py
projects/05_context_memory_session/main.py

项目版保留了短方法名 create()、get()、close()、restore(),同时提供 create_session()、get_session() 等同义入口,方便和本节正文对应。它已经支持关闭后保留历史、恢复写入、按用户筛选、Session 过期,以及用 expected_version 拦截旧版本覆盖。

进入 agent_harness_course_projects 后运行:

python3 --version
python3 projects/05_context_memory_session/main.py

第一条命令确认当前使用 Python 3.9。第二条命令会创建 Session、通过 Manager 追加消息,再完成 Working Memory 的保存、恢复和 Context 组装。这里仍是内存版教学实现:刷新浏览器后,只要服务进程和对象索引还在,Session 可以继续;服务进程重启后则无法恢复,真正的跨重启持久化要等后面的 SQLite 章节接上。

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发
头像-大赛人网
欢迎您留下宝贵的见解!
提交
头像-大赛人网

昵称

取消
昵称

    请登录后查看评论内容