--- title: "第三章:Memory 与 Session(持久化对话)" --- 本章目标:实现对话历史的持久化存储,支持跨进程恢复会话。 > **⚠️ 重要说明:业务层概念 vs 框架概念** > > 本章介绍的 **Memory、Session、Store 是业务层概念**,**不是 Eino 框架的核心组件**。 > > - **Eino 框架层面**:只提供 `adk.Runner` 和 `schema.Message` 等基础抽象,框架本身不关心对话历史的存储方式 > - **业务层层面**:Memory/Session/Store 是本示例项目为了实现持久化对话而设计的业务逻辑,通过组装给 `adk.Runner` 的输入来与 Eino 框架交互 > > 换句话说,Eino 框架只负责"如何处理消息",而"如何存储消息"完全由业务层决定。本章提供的实现只是一个简单的参考示例,你可以根据自己的业务需求选择完全不同的存储方案(数据库、Redis、云存储等)。 ## 代码位置 - 入口代码:[cmd/ch03/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch03/main.go) - Memory 实现:[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 ``` 输出示例: ```text 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` 代表一次完整的对话会话: ```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` 文件: ```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) } ``` **关键代码片段(**注意:这是简化后的代码片段,不能直接运行,完整代码请参考** [cmd/ch03/main.go](https://github.com/cloudwego/eino-examples/blob/main/quickstart/chatwitheino/cmd/ch03/main.go)): ```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.Runner`、`schema.Message` 等基础抽象,不关心消息如何存储 - **业务层(本章实现)**:Memory/Session/Store 是业务层概念,用于管理对话历史的存储 **业务层概念:** - **Memory**:对话历史的持久化存储,支持跨进程恢复 - **Session**:一次完整的对话会话,包含 ID、创建时间、消息列表 - **Store**:管理多个 Session 的存储,支持创建、获取、列表、删除 - **JSONL 格式**:简单的文件格式,易于读写和扩展 **业务层与框架层的交互:** - 业务层负责存储消息,通过 `session.GetMessages()` 获取消息列表 - 将消息列表传递给框架层的 `runner.Run(ctx, history)` 进行处理 - 收集框架层返回的回复,再由业务层保存到存储中 > **💡 提示**:本章的实现只是众多存储方案中的一种简单示例。在实际项目中,你可以根据业务需求选择数据库、Redis、云存储等方案,甚至可以实现更复杂的功能如会话过期清理、搜索、分享等。 ## 扩展思考:业务层存储方案的选择 本章提供的 JSONL 文件存储方案适合简单的单机应用。在实际业务中,你可能需要考虑其他存储方案: **其他存储实现:** - 数据库存储(MySQL、PostgreSQL、MongoDB) - Redis 存储(支持分布式) - 云存储(S3、OSS) **高级功能:** - 会话过期清理 - 会话搜索 - 会话导出/导入 - 会话分享