【AgentScope】4.会话(Session)详解

会话(Session)详解

一句话概括

会话管理就是让 AI Agent 记住"上次聊到哪了",实现跨请求、跨进程、多用户的状态恢复。

你能学到什么

  • 理解为什么 Agent 需要会话管理
  • 掌握双轨存储的设计思想(快照 + 日志)
  • 学会使用 RuntimeContext 配置会话
  • 理解多用户隔离的实现原理

前置知识

详见 学习指南。此外需了解 工作空间(Workspace) 的文件存储基本结构和 JSONL 文件格式。

核心概念

会话(Session)--- 餐厅的点餐单

生活类比:想象你去一家餐厅吃饭,每次来都会有一张专属的点餐单。

  • 点餐单 = 会话(Session)
  • 你的名字 = sessionId(会话ID)
  • 记录的菜品 = 对话历史
  • 下次来继续点菜 = 恢复会话状态

如果餐厅不保存点餐单,你每次去都要重新说一遍"我要一份宫保鸡丁,不要花生"。会话管理就是帮 Agent 准备好这张"点餐单",让它记得之前的对话内容。

双轨存储 --- 快照 + 日志

生活类比:就像你备份手机数据有两种方式:

  1. 快照(Snapshot):定期给手机拍个"全家福",某一时刻的完整状态
  2. 日志(Log):记流水账,每发生一件事就记一笔,永不删除
方式 类比 特点 Agent 中的用途
快照 手机备份 定期保存完整状态 保存 Memory、ToolExecutionContext 等状态
日志 记账本 逐条追加,永不修改 保存完整对话历史,用于审计

为什么需要两种?

  • 快照:恢复快,直接加载就能用
  • 日志:完整记录,不会丢失任何对话细节,可以追溯历史

RuntimeContext --- 餐厅的预订信息

生活类比:打电话预订餐厅时,你要告诉对方:

  • "我叫张三" --- userId(用户身份)
  • "预订今晚8点的桌子" --- sessionId(这顿饭的标识)
  • "我要靠窗的位置" --- 其他上下文信息
java 复制代码
RuntimeContext ctx = RuntimeContext.builder()
    .sessionId("sess-001")    // 这顿"饭"的编号
    .userId("alice")          // "吃饭"的人是谁
    .build();

多用户隔离 --- 餐厅的分区座位

生活类比:一家餐厅要服务很多客人,每个人的点餐单要分开存放:

  • 张三的点餐单放 A 区
  • 李四的点餐单放 B 区
  • 互不干扰,隐私得到保护

在代码中:

java 复制代码
// Alice 和 Bob 用同一个 Agent,但各自独立
agent.call(msg, RuntimeContext.builder()
    .sessionId("alice-1")
    .userId("alice")
    .build());
// 会话文件在:workspace/agents/MyAgent/context/alice-1/

agent.call(msg, RuntimeContext.builder()
    .sessionId("bob-1")
    .userId("bob")
    .build());
// 会话文件在:workspace/agents/MyAgent/context/bob-1/

关键代码解读

1. 创建 RuntimeContext

java 复制代码
// 创建运行时上下文,告诉 Agent "我是谁,这是哪次对话"
RuntimeContext ctx = RuntimeContext.builder()
    .sessionId("sess-001")  // 设置会话 ID,相当于"这顿饭的编号"
    .userId("alice")        // 设置用户 ID,相当于"谁在吃饭"
    .build();

// 用这个上下文调用 Agent
// block() 表示阻塞等待结果(响应式编程)
agent.call(msg, ctx).block();

2. 默认会话与自定义会话

java 复制代码
// 方式一:使用默认会话(最简单)
// HarnessAgent 会自动创建 WorkspaceSession
HarnessAgent.builder()
    .name("MyAgent")
    .model(model)
    .workspace(workspace)
    .build();
// 会话文件存在:workspace/agents/MyAgent/context/

// 方式二:使用自定义路径的会话
HarnessAgent.builder()
    .name("MyAgent")
    .model(model)
    .workspace(workspace)
    .session(new JsonSession(Path.of("/custom/sessions")))  // 指定会话存储位置
    .build();
// 会话文件存在:/custom/sessions/

// 方式三:在调用时临时覆盖会话设置
agent.call(msg, RuntimeContext.builder()
        .sessionId("sess-001")                              // 临时指定会话 ID
        .session(customSession)                             // 临时使用自定义会话
        .sessionKey(SimpleSessionKey.of("sess-001"))       // 会话的键名
        .build())
    .block();

3. 多用户隔离示例

java 复制代码
// 同一个 Agent 实例服务不同用户
// alice 的会话
agent.call(msg, RuntimeContext.builder()
    .sessionId("alice-1")  // alice 的第一个会话
    .userId("alice")       // 用户 ID
    .build())
    .block();

// bob 的会话
agent.call(msg, RuntimeContext.builder()
    .sessionId("bob-1")     // bob 的第一个会话
    .userId("bob")          // 用户 ID
    .build())
    .block();

// 结果:两个用户的会话状态、文件路径完全隔离
// alice 的数据在:context/alice-1/
// bob 的数据在:context/bob-1/

4. bindRuntimeContext 的处理逻辑

java 复制代码
// HarnessAgent.bindRuntimeContext 内部逻辑(简化版)
public void bindRuntimeContext(RuntimeContext ctx) {
    // 1. 补默认值
    // 如果没传 session,就用 defaultSession(默认是 WorkspaceSession)
    Session session = ctx.getSession() != null
        ? ctx.getSession()
        : this.defaultSession;

    // 2. 确定 sessionKey
    // 优先级:传入的 sessionKey > sessionId > agentName
    SessionKey key = ctx.getSessionKey() != null
        ? ctx.getSessionKey()
        : SimpleSessionKey.of(ctx.getSessionId());

    // 3. 预加载状态
    // 如果 session 和 sessionKey 都有,尝试从磁盘恢复之前的状态
    if (session != null && key != null) {
        delegate.loadIfExists(session, key);
    }

    // 4. 同步 userId(用于多租户文件隔离)
    this.userIdRef.set(ctx.getUserId());
}

整体流程图

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                        用户发起请求                                   │
│                  agent.call(msg, ctx)                                │
└─────────────────────────┬───────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────────┐
│                   1. bindRuntimeContext                              │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │ • 补默认 session/sessionKey                                    │  │
│  │ • 调用 loadIfExists 恢复之前保存的状态                          │  │
│  │ • 设置 userId 用于多用户隔离                                    │  │
│  └──────────────────────────────────────────────────────────────┘  │
└─────────────────────────┬───────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────────┐
│                   2. 执行 Agent 逻辑                                 │
│                  (调用 LLM、执行工具等)                               │
└─────────────────────────┬───────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────────┐
│               3. 触发 PostCallEvent / ErrorEvent                    │
└─────────────────────────┬───────────────────────────────────────────┘
                          │
          ┌───────────────┴───────────────┐
          ▼                               ▼
┌──────────────────────┐      ┌──────────────────────┐
│ SessionPersistenceHook │      │   MemoryFlushManager  │
│   (priority: 900)     │      │   (压缩/flush 时触发)  │
└──────────┬───────────┘      └──────────┬───────────┘
           │                             │
           ▼                             ▼
┌──────────────────────┐      ┌──────────────────────┐
│  saveTo(session)     │      │ offloadMessages()     │
│  保存状态快照         │      │ 追写对话日志           │
└──────────┬───────────┘      └──────────┬───────────┘
           │                             │
           ▼                             ▼
┌──────────────────────┐      ┌──────────────────────┐
│  context/<sessionId>/│      │ sessions/<sessionId>  │
│  ├── memory.json     │      │ ├── .jsonl (压缩上下文) │
│  └── *.json          │      │ └── .log.jsonl (完整)  │
│   (StateModule快照)   │      │   (JSONL双文件)        │
└──────────────────────┘      └──────────────────────┘

文件存储结构

复制代码
workspace/
└── agents/
    └── <agentId>/
        ├── context/                    ← WorkspaceSession 负责
        │   └── <sessionId>/            ← 每个会话独立目录
        │       ├── memory.json         ← Agent 的记忆快照
        │       └── *.json              ← 其他 StateModule 快照
        │
        └── sessions/                    ← SessionTree + WorkspaceManager 负责
            ├── sessions.json            ← 会话索引(所有会话概览)
            ├── <sessionId>.jsonl        ← LLM 可见的压缩上下文
            └── <sessionId>.log.jsonl   ← 完整对话日志(永不压缩)

关键区别:

目录 内容 谁负责 特点
context/ 状态快照 WorkspaceSession 恢复快,覆盖写
sessions/ 对话日志 SessionTree 完整记录,追加写

学习要点

1. 会话解决的核心问题

"上次聊到哪了?" --- 这是会话管理要解决的根本问题。没有会话管理,Agent 就像金鱼记忆,每次对话都从零开始。

2. 双轨存储的设计智慧

存储方式 类比 优点 缺点
快照 拍照纪念 恢复快,直接可用 会丢失快照后的变化
日志 记流水账 完整记录,可追溯 恢复慢,需要回放

两者结合,取长补短 --- 这就是工程设计的智慧。

3. RuntimeContext 的三个关键字段

java 复制代码
RuntimeContext.builder()
    .sessionId("xxx")   // 会话唯一标识,决定文件存哪
    .userId("xxx")      // 用户标识,实现多用户隔离
    .session(custom)    // 自定义会话实现(可选)
    .build();

4. 多用户隔离的两个层面

  1. 会话层隔离 :不同 sessionId 的文件放在不同目录
  2. 文件层隔离 :不同 userId 的文件操作有不同路径前缀

5. 常见使用场景

java 复制代码
// 场景一:单用户简单使用(使用默认设置)
agent.call(msg).block();

// 场景二:多用户服务(必须设置 userId)
agent.call(msg, RuntimeContext.builder()
    .sessionId("chat-001")
    .userId("user-alice")
    .build()).block();

// 场景三:恢复历史会话(使用已有 sessionId)
agent.call(msg, RuntimeContext.builder()
    .sessionId("previous-session-id")  // 之前的会话 ID
    .userId("user-alice")
    .build()).block();

与其他模块的关系


⬅️ 上一篇:03-workspace | 📖 学习指南 | ➡️ 下一篇:05-memory

相关推荐
吴阿福|一人公司1 小时前
类变量和实例变量的命名规范有哪些具体的例子?
java·开发语言
eddietao1 小时前
什么是 fail-fast?什么是 fail-safe?
java·面试
程序员小羊!1 小时前
05 JAVA面向对象
java·开发语言
MrJson-架构师1 小时前
AgentScope Java 2.0:打造分布式、企业级智能体底座
java·开发语言·分布式
dyxal2 小时前
电脑里住进了一支AI团队——Marvis(马维斯)完全使用指南
agent
fengxin_rou2 小时前
深入理解Java类加载机制:从原理到实战详解
java·开发语言
糖果店的幽灵2 小时前
Spring AI 从入门到精通-Prompt 工程
java·spring·prompt
小江的记录本2 小时前
【Spring全家桶】Spring Cloud 2023.0.x:配置中心:Nacos Config、Apollo(附《思维导图》+《面试高频考点清单》)
java·spring boot·后端·python·spring·spring cloud·面试
weixin_408318042 小时前
2026年医疗直播行业趋势报告:技术方向、监管变化与市场格局
java·大数据·人工智能