第 13 期:赋予长期记忆 — Window Buffer Memory 与对话状态持久化
为什么 AI Agent 需要记忆?
大模型本身是无状态的——每次调用都是全新的,它不记得上一秒你说了什么。如果不引入 Memory,你的 AI 对话将变成:
sequenceDiagram
participant User as 👤 用户
participant AI as 🤖 无记忆 Agent
User->>AI: 我叫张三
AI-->>User: 你好张三!有什么需要帮助的?
User->>AI: 我刚才告诉你我叫什么?
AI-->>User: 抱歉,我不知道你叫什么。😅
Note over AI: 完全失忆!每次对话都是独立的Memory 节点的作用就是在每次 LLM 调用前,把历史对话注入到 Prompt 中,让模型"看起来"记得之前说过的话。
1. Window Buffer Memory
最常用的记忆策略。它维护一个固定大小的"滑动窗口",只保留最近 N 条消息。
graph TB
subgraph "Window Buffer Memory (窗口大小 = 6)"
direction LR
subgraph "第1轮"
M1["👤 你好"]
M2["🤖 你好!"]
end
subgraph "第2轮"
M3["👤 天气如何?"]
M4["🤖 北京今天晴"]
end
subgraph "第3轮"
M5["👤 明天呢?"]
M6["🤖 明天多云"]
end
subgraph "第4轮 (窗口滑动!)"
M7["👤 后天呢?"]
M8["🤖 后天有雨"]
end
end
M1 -.->|"❌ 被移出窗口"| Trash[🗑️]
M2 -.->|"❌ 被移出窗口"| Trash
style Trash fill:#ef4444,stroke:#dc2626,color:#fff// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Window Buffer Memory 配置详解
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 📝 参数说明:
// - Context Window Length: 10
// 含义: 保留最近 10 条消息 (= 5 轮对话,每轮 1 问 + 1 答)
// ⚠️ 窗口太大 → Token 消耗高,成本增加
// ⚠️ 窗口太小 → Agent 很快忘记上文
// - Session ID Key: sessionId
// 含义: 用于隔离不同用户/不同聊天窗口的记忆
// 引用: {{ $json.sessionId }}
// 效果: 用户 A 的记忆不会泄露给用户 B
// - Session ID: {{ $json.sessionId }}
// 来源: 通常从 Chat Trigger 的输出中获取
窗口大小选择指南
| 场景 | 推荐窗口大小 | 理由 |
|---|---|---|
| 快速问答 (FAQ) | 4-6 条 | 用户通常只需 2-3 轮就能得到答案 |
| 技术支持 | 10-20 条 | 需要记住问题描述与排查过程 |
| 深度咨询 | 30-50 条 | 需要完整的对话上下文 |
| 闲聊 / 陪伴 | 20-30 条 | 保持人格一致性 |
2. Session 隔离机制
graph TB
subgraph "多用户场景的 Session 隔离"
CT[💬 Chat Trigger]
CT -->|"sessionId: user_alice"| Agent
CT -->|"sessionId: user_bob"| Agent
CT -->|"sessionId: user_carol"| Agent
Agent[🤖 AI Agent]
Agent --> Memory[💾 Window Buffer Memory]
Memory --> S1["📂 Session: user_alice
[你好, 查天气, ...]"]
Memory --> S2["📂 Session: user_bob
[帮我写代码, ...]"]
Memory --> S3["📂 Session: user_carol
[产品报价, ...]"]
end
style Memory fill:#22c55e,stroke:#16a34a,color:#fff// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Session ID 的生成策略
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 策略 1: 使用 Chat Trigger 自动生成的 ID (最简单)
// {{ $json.sessionId }}
// 策略 2: 基于用户身份 (适合多渠道)
// {{ $json.platform + '_' + $json.userId }}
// 例: "telegram_12345" 或 "web_67890"
// 策略 3: 基于对话主题 (适合工单系统)
// {{ $json.ticketId }}
// 同一工单的所有对话共享记忆
3. Memory 在 LLM 调用中的实际效果
理解 Memory 的本质:它只是在每次调用 LLM 时,把历史消息注入到消息数组中。
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 无 Memory 时,发给 LLM 的消息:
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const messagesWithoutMemory = [
{ role: "system", content: "你是一个 AI 助手" },
{ role: "user", content: "明天天气怎么样?" } // 只有当前消息
];
// → 模型不知道用户之前问的是北京还是上海
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 有 Memory 时,发给 LLM 的消息:
// Memory 自动注入历史对话到消息数组中
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const messagesWithMemory = [
{ role: "system", content: "你是一个 AI 助手" },
// ↓↓↓ Memory 自动注入的历史 ↓↓↓
{ role: "user", content: "帮我查一下北京的天气" }, // 历史消息 1
{ role: "assistant", content: "北京今天晴,28°C" }, // 历史消息 2
// ↑↑↑ Memory 注入结束 ↑↑↑
{ role: "user", content: "明天呢?" } // 当前消息
];
// → 模型看到完整上下文,知道"明天呢"指的是"北京明天的天气"
4. 记忆清除与管理
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 在工作流中主动清除某个 Session 的记忆
// 适用场景: 用户说"重新开始" / 工单关闭时清理
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 方法: 在 Code 节点中通过 n8n 内部 API 操作
// 或者在 Agent System Prompt 中定义:
// "当用户说'重新开始'时,回复'好的,让我们重新开始吧!'并忽略之前的对话内容。"
// ⚠️ 注意: Memory 默认存储在 n8n 的内存中
// 容器重启后所有 Memory 会丢失!
// 生产环境建议使用外部存储 (如 Redis)
Memory 类型对比
| Memory 类型 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| Window Buffer | 保留最近 N 条消息 | 简单可靠、开箱即用 | 窗口外的内容完全丢失 |
| Token Buffer | 按 Token 数量截断 | 精确控制 LLM 上下文 | 配置略复杂 |
| Summary | 用 LLM 生成历史摘要 | 能保留更长历史要点 | 摘要可能丢细节,额外调用成本 |
| Vector Store | 将历史嵌入向量数据库 | 实现真正的"长期记忆" | 需要配置向量数据库 |
下一步
在 Ep 14 中,我们将给 Agent 配备真正的外部工具 (Tools)——让它不仅能"说",还能"做":计算、搜索、调用 API。