Cells(单元):添加记忆和状态
"我们就是我们的记忆,我们是那个不断变化形态的幻影博物馆,那堆破碎的镜子。" —— 豪尔赫·路易斯·博尔赫斯
从分子到单元
我们已经探索了原子(单个指令)和分子(带示例的指令)。现在我们上升到单元 —— 具有记忆的上下文结构,能够在多次交互中持久存在。
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ 单元 = [指令] + [示例] + [记忆/状态] + [当前输入] │
│ │
└─────────────────────────────────────────────────────────────────────────────┘就像生物细胞在与环境交互时维持其内部状态一样,我们的上下文"单元"在与 LLM 的多次交换中保存信息。
记忆问题
默认情况下,LLM 没有记忆。每个请求都是独立处理的:
┌───────────────────────┐ ┌───────────────────────┐
│ 请求 1 │ │ 请求 2 │
├───────────────────────┤ ├───────────────────────┤
│ "我叫 Alex。" │ │ "我叫什么名字?" │
│ │ │ │
│ │ │ │
└───────────────────────┘ └───────────────────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ 响应 1 │ │ 响应 2 │
├───────────────────────┤ ├───────────────────────┤
│ "你好 Alex,很高兴 │ │ "我无法访问之前的 │
│ 认识你。" │ │ 对话..." │
│ │ │ │
└───────────────────────┘ └───────────────────────┘没有记忆,LLM 会忘记之前交互的信息,造成脱节、令人沮丧的用户体验。
单元解决方案:对话记忆
最简单的单元结构是在上下文中添加对话历史:
┌───────────────────────────────────────────────────────────────────────┐
│ │
│ 系统提示:"你是一个有帮助的助手..." │
│ │
│ 对话历史: │
│ 用户:"我叫 Alex。" │
│ 助手:"你好 Alex,很高兴认识你。" │
│ │
│ 当前输入:"我叫什么名字?" │
│ │
└───────────────────────────────────────────────────────────────────────┘现在 LLM 可以访问之前的交换并保持连续性。
记忆 Token 预算问题
随着对话的增长,上下文窗口会被填满。我们需要记忆管理策略:
[上下文窗口 Token]
┌─────────────────────────────────────────────┐
│ │
轮次 1 │ 系统指令 用户输入 1 │
│ │
├─────────────────────────────────────────────┤
│ │
轮次 2 │ 系统 历史 1 用户输入 2 │
│ │
├─────────────────────────────────────────────┤
│ │
轮次 3 │ 系统 历史 1 历史 2 用户输入 3 │
│ │
├─────────────────────────────────────────────┤
│ │
轮次 4 │ 系 历史 1-3 用户输入 4 │
│ │
├─────────────────────────────────────────────┤
│ │
轮次 5 │ 历史 2-4 用户输入 5 │
│ │
└─────────────────────────────────────────────┘
▲
│
最终,必须放弃某些内容记忆管理策略
几种策略有助于优化有限的上下文窗口使用:
┌───────────────────────────────────────────────────────────────────┐
│ 记忆管理策略 │
├────────────────────┬──────────────────────────────────────────────┤
│ 窗口法 │ 只保留最近的 N 个轮次 │
├────────────────────┼──────────────────────────────────────────────┤
│ 摘要法 │ 将较旧的轮次压缩成摘要 │
├────────────────────┼──────────────────────────────────────────────┤
│ 键值存储 │ 单独提取和存储重要事实 │
├────────────────────┼──────────────────────────────────────────────┤
│ 优先级裁剪 │ 首先删除较不重要的轮次 │
├────────────────────┼──────────────────────────────────────────────┤
│ 语义分块 │ 将相关的交换组合在一起 │
└────────────────────┴──────────────────────────────────────────────┘窗口法:滑动上下文
最简单的记忆管理方法只保留最近的对话轮次:
┌───────────────────────────┐
轮次 1 │ 系统 + 轮次 1 │
└───────────────────────────┘
│
▼
┌───────────────────────────┐
轮次 2 │ 系统 + 轮次 1-2 │
└───────────────────────────┘
│
▼
┌───────────────────────────┐
轮次 3 │ 系统 + 轮次 1-3 │
└───────────────────────────┘
│
▼
┌───────────────────────────┐
轮次 4 │ 系统 + 轮次 2-4 │ ← 轮次 1 被丢弃
└───────────────────────────┘
│
▼
┌───────────────────────────┐
轮次 5 │ 系统 + 轮次 3-5 │ ← 轮次 2 被丢弃
└───────────────────────────┘这种方法简单但会忘记早期轮次的信息。
摘要法:压缩记忆
更复杂的方法是将较旧的轮次压缩成摘要:
┌────────────────────────────────────────────┐
轮次 1-3 │ 系统 + 轮次 1-3 │
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
轮次 4 │ 系统 + 摘要(轮次 1-2) + 轮次 3-4 │
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
轮次 5 │ 系统 + 摘要(轮次 1-3) + 轮次 4-5 │
└────────────────────────────────────────────┘摘要在减少 token 数量的同时保留关键信息。
键值记忆:结构化状态
为了更好的控制,我们可以以结构化格式提取和存储重要事实:
┌─────────────────────────────────────────────────────────────────────┐
│ 上下文窗口 │
│ │
│ 系统提示:"你是一个有帮助的助手..." │
│ │
│ 记忆: │
│ { │
│ "user_name": "Alex", │
│ "favorite_color": "蓝色", │
│ "location": "多伦多", │
│ "last_topic": "度假计划" │
│ } │
│ │
│ 最近对话: │
│ 用户:"你会推荐什么活动?" │
│ 助手:"考虑到你在多伦多的位置和兴趣..." │
│ │
│ 当前输入:"室内活动怎么样?天气很冷。" │
│ │
└─────────────────────────────────────────────────────────────────────┘这种结构化方法允许精确控制保留什么信息。
超越对话:有状态的应用
单元不仅能实现连贯的对话。它们允许有状态的应用,其中 LLM 可以:
- 记住之前的交互
- 更新和维护变量
- 跟踪多步骤过程的进度
- 在之前的输出基础上构建
让我们探索一个简单的计算器示例:
┌─────────────────────────────────────────────────────────────────────┐
│ 有状态计算器 │
│ │
│ 系统:"你是一个维持累计总数的计算器助手。按照用户的数学操作 │
│ 逐步执行。" │
│ │
│ 状态:{ "current_value": 0 } │
│ │
│ 用户:"从 5 开始" │
│ 助手:"从 5 开始。当前值为 5。" │
│ 状态:{ "current_value": 5 } │
│ │
│ 用户:"乘以 3" │
│ 助手:"5 × 3 = 15。当前值为 15。" │
│ 状态:{ "current_value": 15 } │
│ │
│ 用户:"加 7" │
│ 助手:"15 + 7 = 22。当前值为 22。" │
│ 状态:{ "current_value": 22 } │
│ │
└─────────────────────────────────────────────────────────────────────┘状态变量在多个轮次中持久存在,实现连续计算。
长期记忆:超越上下文窗口
对于真正持久的记忆,我们需要外部存储:
┌──────────────────────────────────────────────────────────────────────────┐
│ │
│ 用户输入 │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 提取 │ │
│ │ 关键信息 │ │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌────────────────────┐ │
│ │ 更新 │◄─────┤ 外部记忆 │ │
│ │ 记忆 │ │ (向量数据库, │ │
│ │ │─────►│ 文档数据库等) │ │
│ └─────────────┘ └────────────────────┘ │
│ │ ▲ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────┐ ┌────────────────────┐ │
│ │ 构建 │ │ 检索相关 │ │
│ │ 上下文 │◄─────┤ 记忆 │ │
│ │ │ │ │ │
│ └─────────────┘ └────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ │ │
│ │ LLM │ │
│ │ │ │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ 响应 │
│ │
└──────────────────────────────────────────────────────────────────────────┘这种架构通过以下方式实现潜在的无限记忆:
- 从对话中提取关键信息
- 将其存储在外部数据库中
- 在需要时检索相关上下文
- 将该上下文纳入提示
单元实现:记忆管理器
这是一个实现基本记忆管理的 Python 类:
class ContextCell:
"""一个在交互间维持记忆的上下文单元。"""
def __init__(self, system_prompt, max_turns=10, memory_strategy="window"):
"""
初始化上下文单元。
参数:
system_prompt (str): 系统指令
max_turns (int): 保留的最大对话轮次
memory_strategy (str): 'window'、'summarize' 或 'key_value'
"""
self.system_prompt = system_prompt
self.max_turns = max_turns
self.memory_strategy = memory_strategy
self.conversation_history = []
self.key_value_store = {}
def add_exchange(self, user_input, assistant_response):
"""将对话交换添加到历史。"""
self.conversation_history.append({
"user": user_input,
"assistant": assistant_response
})
# 如果需要,应用记忆管理
if len(self.conversation_history) > self.max_turns:
self._manage_memory()
def extract_info(self, key, value):
"""在键值存储中存储重要信息。"""
self.key_value_store[key] = value
def _manage_memory(self):
"""应用选定的记忆管理策略。"""
if self.memory_strategy == "window":
# 只保留最近的轮次
self.conversation_history = self.conversation_history[-self.max_turns:]
elif self.memory_strategy == "summarize":
# 摘要较旧的轮次(实践中会使用 LLM)
to_summarize = self.conversation_history[:-self.max_turns + 1]
summary = self._create_summary(to_summarize)
# 用摘要替换旧轮次
self.conversation_history = [{"summary": summary}] + \
self.conversation_history[-(self.max_turns-1):]
def _create_summary(self, exchanges):
"""创建对话交换的摘要。"""
# 实践中,这会调用 LLM 来创建摘要
# 在这个例子中,我们使用占位符
return f"前 {len(exchanges)} 次交换的摘要"
def build_context(self, current_input):
"""为下一次 LLM 调用构建完整上下文。"""
context = f"{self.system_prompt}\n\n"
# 如果有键值记忆,添加它
if self.key_value_store:
context += "记忆:\n"
for key, value in self.key_value_store.items():
context += f"{key}: {value}\n"
context += "\n"
# 添加对话历史
if self.conversation_history:
context += "对话历史:\n"
for exchange in self.conversation_history:
if "summary" in exchange:
context += f"[之前的交换:{exchange['summary']}]\n\n"
else:
context += f"用户:{exchange['user']}\n"
context += f"助手:{exchange['assistant']}\n\n"
# 添加当前输入
context += f"用户:{current_input}\n助手:"
return context测量单元效率
与分子一样,测量效率对单元至关重要:
┌─────────────────────────────────────────────────────────────────┐
│ 记忆策略比较 │
├──────────────────┬──────────────┬─────────────┬─────────────────┤
│ 策略 │ Token 使用 │ 信息保留 │ 实现复杂度 │
│ │ │ │ │
├──────────────────┼──────────────┼─────────────┼─────────────────┤
│ 无记忆 │ 最低 │ 无 │ 简单 │
├──────────────────┼──────────────┼─────────────┼─────────────────┤
│ 完整历史 │ 最高 │ 完整 │ 简单 │
├──────────────────┼──────────────┼─────────────┼─────────────────┤
│ 窗口法 │ 可控 │ 仅最近 │ 容易 │
├──────────────────┼──────────────┼─────────────┼─────────────────┤
│ 摘要法 │ 中等 │ 良好 │ 中等 │
├──────────────────┼──────────────┼─────────────┼─────────────────┤
│ 键值存储 │ 低 │ 选择性 │ 中等 │
├──────────────────┼──────────────┼─────────────┼─────────────────┤
│ 外部存储 │ 非常低 │ 广泛 │ 复杂 │
└──────────────────┴──────────────┴─────────────┴─────────────────┘不同的策略针对不同的优先级进行优化。选择正确的方法取决于您的具体应用需求。
高级技术:记忆编排
对于复杂的应用,多个记忆系统可以协同工作:
┌─────────────────────────────────────────────────────────────────────┐
│ 记忆编排 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ │ │ │ │ │ │
│ │ 短期 │ │ 工作 │ │ 长期 │ │
│ │ 记忆 │ │ 记忆 │ │ 记忆 │ │
│ │ │ │ │ │ │ │
│ │ • 最近轮次 │ │ • 当前任务 │ │ • 用户资料 │ │
│ │ • 即时上下文 │ │ • 活跃变量 │ │ • 历史事实 │ │
│ │ • 最后几次 │ │ • 任务进度 │ │ • 学习的 │ │
│ │ 交换 │ │ • 任务中状态 │ │ 偏好 │ │
│ │ │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ ▲ ▼ ▲ ▼ ▲ ▼ │
│ │ │ │ │ │ │ │
│ ┌──────┘ └───────────────────┘ └───────────────────┘ └──────┐ │
│ │ │ │
│ │ 记忆管理器 │ │
│ │ │ │
│ └───────────────────────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ │ │
│ │ 上下文 │ │
│ │ 构建器 │ │
│ │ │ │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ │ │
│ │ LLM │ │
│ │ │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘这种架构模仿人类记忆系统,包括:
- 短期记忆:最近的对话轮次
- 工作记忆:活跃的任务状态和变量
- 长期记忆:持久的用户信息和偏好
记忆管理器编排这些系统,决定在每个上下文中包含什么信息。
记忆与减少幻觉
记忆单元最有价值的好处之一是减少幻觉:
┌─────────────────────────────────────────────────────────────────────┐
│ 减少幻觉策略 │
├─────────────────────────────────────────────────────────────────────┤
│ 1. 明确存储从之前交换中提取的事实 │
│ 2. 为信息标记来源/确定性级别 │
│ 3. 当出现类似主题时在上下文中包含相关事实 │
│ 4. 检测并纠正记忆与响应之间的矛盾 │
│ 5. 通过用户确认定期验证重要事实 │
└─────────────────────────────────────────────────────────────────────┘通过将 LLM 根植于记忆中的一致事实,我们可以大幅提高可靠性。
超越文本:结构化状态
高级单元维护的不仅是文本历史的结构化状态:
┌─────────────────────────────────────────────────────────────────────┐
│ 结构化状态示例 │
├─────────────────────────┬───────────────────────────────────────────┤
│ 进度状态 │ {"step": 3, "completed_steps": [1, 2], │
│ │ "next_action": "validate_input"} │
├─────────────────────────┼───────────────────────────────────────────┤
│ 用户资料 │ {"name": "Alex", "preferences": { │
│ │ "communication_style": "简洁", │
│ │ "expertise_level": "初学者"}} │
├─────────────────────────┼───────────────────────────────────────────┤
│ 应用状态 │ {"current_view": "dashboard", │
│ │ "filters": ["active", "high_priority"], │
│ │ "sort_by": "deadline"} │
├─────────────────────────┼───────────────────────────────────────────┤
│ 环境上下文 │ {"location": "多伦多", │
│ │ "weather": "下雪", │
│ │ "time": "晚上"} │
└─────────────────────────┴───────────────────────────────────────────┘这种结构化方法允许对上下文进行精确控制,并支持更复杂的应用。
记忆反馈循环
复杂的单元创建反馈循环,其中 LLM 帮助管理自己的记忆:
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 用户:"我计划下个月去日本旅行。" │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ [内部记忆提取] ││
│ │ 需要记住的重要事实: ││
│ │ - 用户计划去日本旅行 ││
│ │ - 旅行安排在下个月 ││
│ │ 置信度:高 ││
│ └─────────────────────────────────────────────────────────────────┘│
│ │
│ 助手:"太令人兴奋了!日本很美。你对东京和京都这样的城市感兴趣, │
│ 还是更偏爱乡村地区?" │
│ │
│ 用户:"肯定是东京,也许还有大阪。" │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ [内部记忆更新] ││
│ │ 更新的事实: ││
│ │ - 用户计划下个月去日本旅行 ││
│ │ - 用户对东京和大阪感兴趣 ││
│ │ - 用户可能对乡村地区不感兴趣(置信度:中等) ││
│ └─────────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────────┘LLM 本身提取和更新需要记住的重要信息,创建一个自我改进的记忆系统。
关键要点
- 记忆单元在多次交互中添加状态持久性
- Token 预算管理随着对话增长至关重要
- 记忆策略包括窗口法、摘要法和键值存储
- 外部记忆支持超出上下文窗口的无限持久存储
- 结构化状态支持超越简单对话的复杂应用
- 记忆编排结合多个记忆系统以获得最佳性能
- 自我改进的记忆使用 LLM 帮助管理自己的记忆
练习
- 实现一个带窗口法的简单对话记忆系统
- 在同一个扩展对话上比较不同的记忆策略
- 构建一个从对话中提取重要事实的键值存储
- 尝试使用 LLM 来摘要较旧的对话轮次
- 为特定应用领域创建一个结构化状态管理器
下一步
在下一节中,我们将探索器官 —— 多智能体系统,其中多个上下文单元协同工作以解决复杂问题。
继续阅读 04_organs_applications.md →
深入探讨:记忆抽象
记忆可以组织成多个抽象层:
┌────────────────────────────────────────────────────────────────────┐
│ 记忆抽象层 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ 情景记忆 │ 特定的对话交换和事件 │
│ └─────────────────┘ │
│ ▲ │
│ │ │
│ ┌─────────────────┐ │
│ │ 语义记忆 │ 事实、概念和结构化知识 │
│ └─────────────────┘ │
│ ▲ │
│ │ │
│ ┌─────────────────┐ │
│ │ 概念记忆 │ 高级模式、偏好、目标 │
│ │ │ │
│ └─────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘这种分层方法允许系统在具体细节和对交互上下文的高级理解之间取得平衡。