---
tags: [eino, ai-development, go, quickstart, memory, session]
create time: 2026-04-29 15:30
title: 第三章：Memory 与 Session（持久化对话）
weight: 3
---

## 概述

在第二章掌握了多轮对话的实现后，我们面临一个关键问题：**对话历史只存在于内存中，进程退出后一切归零**。本章引入 **Memory 与 Session** 机制，让对话历史能够持久化保存并跨进程恢复，为构建真正的智能助手奠定基础。

> [!warning] 重要概念区分：业务层 vs 框架层
> 本章介绍的 **Memory、Session、Store 是业务层概念**，**不是 Eino 框架的核心组件**。Eino 框架只负责"如何处理消息"，而"如何存储消息"完全由业务层决定。本章提供的实现只是一个参考示例，你可以根据自己的需求选择数据库、Redis、云存储等方案。

## 代码位置

- 入口代码：[cmd/ch03/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch03/main.go)
- Store 实现：[mem/store.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/mem/store.go)

---

## 前置条件

与第二章一致：需要配置一个可用的 ChatModel（OpenAI 或 Ark）。

## 运行

在 `examples/quickstart/chatwitheino` 目录下执行：

```bash
# 创建新会话
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
```

---

<!-- @block-anchor:overview:start -->
<!-- @block-anchor:overview:end -->

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

> [!question] 思考一下
> 第二章我们实现了多轮对话，但存在一个问题——如果进程退出、机器重启，之前聊的内容还会在吗？

**内存存储的局限：**

- 进程退出后，对话历史丢失
- 无法跨设备、跨进程恢复会话
- 无法实现会话管理（列表、删除、搜索等）

**Memory 的定位：**

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

**简单类比：**

| 方式 | 比喻 | 特点 |
|------|------|------|
| 内存存储 | "草稿纸" | 进程退出就没了 |
| Memory | "笔记本" | 永久保存，随时翻阅 |

---

## 关键概念

> [!tip] 重要提示
> 以下 Session、Store 等概念都是**业务层实现**，用于管理对话历史的存储。Eino 框架本身不提供这些组件，而是由业务层负责管理消息列表，然后将消息传递给 `adk.Runner` 进行处理。

### Session（业务层概念）

`Session` 代表一次完整的对话会话：

```go
type Session struct {
    ID        string
    CreatedAt time.Time

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

**核心方法：**

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

### Store（业务层概念）

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

```go
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

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

### 2. 获取或创建 Session

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

### 3. 追加用户消息

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

### 4. 获取历史并调用 Agent

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

### 5. 追加助手消息

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

### 关键代码解析

完整流程可以浓缩为以下核心片段：

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

// 2. 读取用户输入并追加到会话
userMsg := schema.UserMessage(line)
if err := session.Append(userMsg); err != nil {
    log.Fatal(err)
}

// 3. 获取全部历史，送入 Agent 处理
history := session.GetMessages()
events := runner.Run(ctx, history)
content := collectAssistantFromEvents(events)

// 4. 收集回复并存回会话
assistantMsg := schema.AssistantMessage(content, nil)
if err := session.Append(assistantMsg); err != nil {
    log.Fatal(err)
}
```

> [!note] 简化说明
> 以上代码已省略错误处理之外的细节（如命令行参数解析、事件流消费等），不能直接运行。完整代码请参考 [cmd/ch03/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch03/main.go)。

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

> [!tip] 理解要点
> - **Session 是业务层概念**：由你的代码实现和管理，负责存储和加载对话历史
> - **Agent（Runner）是框架层概念**：由 Eino 框架提供，负责处理消息并生成回复
> - **两者的交互点**：业务层通过 `session.GetMessages()` 获取消息列表，传递给 `runner.Run(ctx, history)` 进行处理

**数据流示意图：**

```mermaid
flowchart TD
    A["用户输入"] --> B["session.Append()<br/>保存用户消息"]
    B --> C["session.GetMessages()<br/>获取完整历史"]
    C --> D["runner.Run(history)<br/>Agent 处理消息"]
    D --> E["收集助手回复"]
    E --> F["session.Append()<br/>保存助手消息"]

    style B fill:#e8f5e9
    style C fill:#e8f5e9
    style F fill:#e8f5e9
    style D fill:#fff3e0
```

**分层面貌：**

```mermaid
graph LR
    subgraph biz_layer["业务层 — 你的代码"]
        S1["Session<br/>持久化存储"]
        S2["GetMessages()"]
        S3["Append()<br/>保存消息"]
        S1 --> S2
        S3 -.->|"回写"| S1
    end

    subgraph frame_layer["框架层 — Eino"]
        R1["runner.Run()"]
    end

    S2 --> R1
    R1 -->|"助手回复"| S3

    style S1 fill:#e3f2fd
    style S2 fill:#e3f2fd
    style S3 fill:#e3f2fd
    style R1 fill:#fff3e0
    style biz_layer fill:none,stroke:#90caf9
    style frame_layer fill:none,stroke:#ffcc80
```

## 本章小结

| 核心概念 | 定位 | 说明 |
|----------|------|------|
| **Memory** | 业务层 | 对话历史的持久化存储，支持跨进程恢复 |
| **Session** | 业务层 | 一次完整的对话会话，包含 ID、创建时间、消息列表 |
| **Store** | 业务层 | 管理多个 Session 的存储，支持创建、获取、列表、删除 |
| **JSONL 格式** | 业务层 | 简单的文件格式，易于读写和扩展 |
| **adk.Runner** | 框架层 | 接收消息列表，调用 ChatModel，返回回复 |

> [!success] 学习成果
> 完成本章后，你应该能够：
> - 理解 Memory/Session/Store 的业务层职责
> - 知道如何将对话历史持久化为 JSONL 文件
> - 明白业务层与框架层的协作边界
> - 根据业务需求选择合适的存储方案

---

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

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

<table>
<tr><td>存储方案</td><td>适用场景</td><td>优势</td><td>劣势</td></tr>
<tr><td><strong>JSONL 文件</strong></td><td>单机应用、开发调试</td><td>零依赖，简单直观</td><td>不支持并发、分布式</td></tr>
<tr><td><strong>SQLite / LevelDB</strong></td><td>桌面端应用</td><td>轻量级嵌入式数据库</td><td>不适合高并发写入</td></tr>
<tr><td><strong>MySQL / PostgreSQL</strong></td><td>服务端部署</td><td>成熟稳定，功能丰富</td><td>运维成本较高</td></tr>
<tr><td><strong>Redis</strong></td><td>分布式、高频访问</td><td>性能极高，支持过期策略</td><td>数据需额外持久化</td></tr>
<tr><td><strong>S3 / OSS</strong></td><td>海量冷数据归档</td><td>成本极低，无限扩展</td><td>不适合频繁查询</td></tr>
</table>

**高级功能展望：**

- 会话过期清理（TTL 自动删除）
- 会话全文搜索
- 会话导出 / 导入
- 会话分享（生成公开链接）

> [!tip] Middleware 联动
> 当对话非常长时，单纯增加存储容量是不够的。第五章介绍的 **Summarization Middleware** 可以在调用 Agent 之前自动压缩历史消息，有效控制 Token 消耗。

---

## 下一章预告

[[Eino/quick_start/chapter_04_tool_and_filesystem|第四章：Tool 与文件系统]] 将为 Agent 添加文件访问能力，让智能助手能够读取代码仓库中的真实内容。

## 关联笔记

- [[Eino/quick_start/chapter_02_chatmodelagent_runner_agentevent]]
- [[Eino/quick_start/chapter_04_tool_and_filesystem]]
