第三章:Memory 与 Session(持久化对话)

本章目标:实现对话历史的持久化存储,支持跨进程恢复会话。

⚠️ 重要说明:业务层概念 vs 框架概念

本章介绍的 Memory、Session、Store 是业务层概念不是 Eino 框架的核心组件

换句话说,Eino 框架只负责"如何处理消息",而"如何存储消息"完全由业务层决定。本章提供的实现只是一个简单的参考示例,你可以根据自己的业务需求选择完全不同的存储方案(数据库、Redis、云存储等)。

代码位置

前置条件

与第一章一致:需要配置一个可用的 ChatModel(OpenAI 或 Ark)。

运行

examples/quickstart/chatwitheino 目录下执行:

# 创建新会话
go run ./cmd/ch03

# 恢复已有会话
go run ./cmd/ch03 --session <session-id>

输出示例:

Created new session: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
Session title: New Session
Enter your message (empty line to exit):
you> 你好,我是张三
[assistant] 你好张三!很高兴认识你...
you> 我叫什么名字?
[assistant] 你叫张三...

Session saved: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
Resume with: go run ./cmd/ch03 --session 083d16da-6b13-4fe6-afb0-c45d8f490ce1

从内存到持久化:为什么需要 Memory

第二章我们实现了多轮对话,但有一个问题:对话历史只存在于内存中

内存存储的局限:

  • 进程退出后,对话历史丢失
  • 无法跨设备、跨进程恢复会话
  • 无法实现会话管理(列表、删除、搜索等)

Memory 的定位:

  • Memory 是对话历史的持久化存储:将对话保存到磁盘或数据库
  • Memory 支持 Session 管理:每个 Session 代表一次完整的对话
  • Memory 与 Agent 解耦:Agent 不关心存储细节,只关心消息列表

简单类比:

  • 内存存储 = “草稿纸”(进程退出就没了)
  • Memory = “笔记本”(永久保存,随时翻阅)

关键概念

再次强调:以下 Session、Store 等概念都是业务层实现,用于管理对话历史的存储。Eino 框架本身不提供这些组件,而是由业务层负责管理消息列表,然后将消息传递给 adk.Runner 进行处理。

Session(业务层概念)

Session 代表一次完整的对话会话:

type Session struct {
    ID        string
    CreatedAt time.Time

    messages []*schema.Message  // 对话历史
    // ...
}

核心方法:

  • Append(msg):追加消息到会话,并持久化
  • GetMessages():获取所有消息
  • Title():从第一条用户消息生成会话标题

Store(业务层概念)

Store 管理多个 Session 的持久化存储:

type Store struct {
    dir   string              // 存储目录
    cache map[string]*Session // 内存缓存
}

核心方法:

  • GetOrCreate(id):获取或创建 Session
  • List():列出所有 Session
  • Delete(id):删除 Session

JSONL 文件格式

每个 Session 存储为一个 .jsonl 文件:

{"type":"session","id":"083d16da-...","created_at":"2026-03-11T10:00:00Z"}
{"role":"user","content":"你好,我是谁?"}
{"role":"assistant","content":"你好!我暂时不知道你是谁..."}
{"role":"user","content":"我叫张三"}
{"role":"assistant","content":"好的,张三,很高兴认识你!"}

为什么用 JSONL?

  • 简单:每行一个 JSON 对象,易于读写
  • 可扩展:可以追加新消息,无需重写整个文件
  • 可读性好:可以用文本编辑器直接查看
  • 容错性强:单行损坏不影响其他行

Memory 的实现(业务层示例)

以下是一个简单的业务层实现示例,使用 JSONL 文件存储对话历史。这只是众多可能实现中的一种,你可以根据实际需求选择数据库、Redis 等其他存储方案。

1. 创建 Store

sessionDir := "./data/sessions"
store, err := mem.NewStore(sessionDir)
if err != nil {
    log.Fatal(err)
}

2. 获取或创建 Session

sessionID := "083d16da-6b13-4fe6-afb0-c45d8f490ce1"
session, err := store.GetOrCreate(sessionID)
if err != nil {
    log.Fatal(err)
}

3. 追加用户消息

userMsg := schema.UserMessage("你好")
if err := session.Append(userMsg); err != nil {
    log.Fatal(err)
}

4. 获取历史并调用 Agent

history := session.GetMessages()
events := runner.Run(ctx, history)
content := collectAssistantFromEvents(events)

5. 追加助手消息

assistantMsg := schema.AssistantMessage(content, nil)
if err := session.Append(assistantMsg); err != nil {
    log.Fatal(err)
}

**关键代码片段(注意:这是简化后的代码片段,不能直接运行,完整代码请参考 cmd/ch03/main.go):

// 创建或恢复 Session
session, err := store.GetOrCreate(sessionID)
if err != nil {
    log.Fatal(err)
}

// 用户输入
userMsg := schema.UserMessage(line)
if err := session.Append(userMsg); err != nil {
    log.Fatal(err)
}

// 调用 Agent
history := session.GetMessages()
events := runner.Run(ctx, history)
content := collectAssistantFromEvents(events)

// 保存助手回复
assistantMsg := schema.AssistantMessage(content, nil)
if err := session.Append(assistantMsg); err != nil {
    log.Fatal(err)
}

Session 与 Agent 的关系:业务层与框架层的协作

关键理解:

  • Session 是业务层概念:由业务代码实现和管理,负责存储和加载对话历史
  • Agent(Runner)是框架层概念:由 Eino 框架提供,负责处理消息并生成回复
  • 两者的交互点:业务层通过 session.GetMessages() 获取消息列表,传递给 runner.Run(ctx, history) 进行处理

架构分层:

┌─────────────────────────────────────────────────────────────┐
│                     业务层(你的代码)                          │
│  ┌─────────────┐    ┌──────────────┐    ┌───────────────┐  │
│  │   Session   │───→│ GetMessages() │───→│ runner.Run()  │  │
│  │   (存储)    │    │  (消息列表)    │    │  (框架调用)    │  │
│  └─────────────┘    └──────────────┘    └───────────────┘  │
│         ↑                                      │            │
│         │                                      ↓            │
│  ┌─────────────┐                      ┌───────────────┐    │
│  │   Append()  │←─────────────────────│  助手回复      │    │
│  │  (保存消息)  │                      └───────────────┘    │
│  └─────────────┘                                           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                   框架层(Eino 框架)                          │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  adk.Runner:接收消息列表,调用 ChatModel,返回回复     │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

流程图:

┌─────────────────────────────────────────┐
│  用户输入                                 │
└─────────────────────────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  session.Append()    │
        │  保存用户消息         │
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  session.GetMessages()│
        │  获取完整历史         │
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  runner.Run(history) │
        │  Agent 处理消息       │
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  收集助手回复         │
        └──────────────────────┘
                   ↓
        ┌──────────────────────┐
        │  session.Append()    │
        │  保存助手消息         │
        └──────────────────────┘

本章小结

框架层 vs 业务层:

  • Eino 框架层:提供 adk.Runnerschema.Message 等基础抽象,不关心消息如何存储
  • 业务层(本章实现):Memory/Session/Store 是业务层概念,用于管理对话历史的存储

业务层概念:

  • Memory:对话历史的持久化存储,支持跨进程恢复
  • Session:一次完整的对话会话,包含 ID、创建时间、消息列表
  • Store:管理多个 Session 的存储,支持创建、获取、列表、删除
  • JSONL 格式:简单的文件格式,易于读写和扩展

业务层与框架层的交互:

  • 业务层负责存储消息,通过 session.GetMessages() 获取消息列表
  • 将消息列表传递给框架层的 runner.Run(ctx, history) 进行处理
  • 收集框架层返回的回复,再由业务层保存到存储中

💡 提示:本章的实现只是众多存储方案中的一种简单示例。在实际项目中,你可以根据业务需求选择数据库、Redis、云存储等方案,甚至可以实现更复杂的功能如会话过期清理、搜索、分享等。

扩展思考:业务层存储方案的选择

本章提供的 JSONL 文件存储方案适合简单的单机应用。在实际业务中,你可能需要考虑其他存储方案:

其他存储实现:

  • 数据库存储(MySQL、PostgreSQL、MongoDB)
  • Redis 存储(支持分布式)
  • 云存储(S3、OSS)

高级功能:

  • 会话过期清理
  • 会话搜索
  • 会话导出/导入
  • 会话分享