Agent 记忆处理机制详解:从变量数据类型到内存模型

5 阅读7分钟

一、什么是 Agent 的记忆

在大语言模型(LLM)驱动的智能体(Agent)系统中,记忆(Memory) 是指 Agent 存储、检索和利用历史交互信息的能力。与人类的记忆类似,Agent 的记忆使其能够理解上下文、保持对话连贯性,并基于历史经验做出更合理的决策。

本文基于 LangChain 框架的 InMemoryChatMessageHistory 实现,从变量数据类型的粒度深入剖析 Agent 记忆处理的底层机制。


二、记忆系统的核心数据结构

2.1 消息(Message):记忆的基本单元

消息是构成对话历史的最小单元。在 LangChain 中,消息有多种类型,每种类型对应不同的角色和数据结构。

2.1.1 消息基类结构

每条消息本质上是一个具有以下核心属性的对象:

属性名数据类型说明
contentstring消息的文本内容
typestring消息类型标识,如 'human''system''ai'
name`stringundefined`可选的发送者名称
additional_kwargsRecord<string, any>额外的关键字参数,用于扩展功能
2.1.2 三种核心消息类型

1. SystemMessage(系统消息)

const systemMessage = new SystemMessage("现在开始你的身份是一个友好,幽默的做菜助手...");
  • 类型标识type = 'system'
  • 作用:定义 AI 的角色、行为准则和输出风格
  • 生命周期:通常在对话开始时设置,贯穿整个会话
  • 数据特征contentstring 类型,内容由开发者预设

2. HumanMessage(人类消息)

const userMessage1 = new HumanMessage("你今天吃什么?");
  • 类型标识type = 'human'

  • 作用:代表用户输入的消息

  • 数据特征

    • contentstring 类型,用户输入的文本
    • 可包含多媒体内容(图片、文件等),此时 content 为数组类型

3. AIMessage(AI 消息)

const response1 = await model.invoke(messages1);
// response1 是 AIMessage 实例
  • 类型标识type = 'ai'

  • 作用:代表模型生成的回复

  • 数据特征

    • contentstring 类型,模型输出的文本
    • additional_kwargs:可能包含 tool_calls(工具调用信息)等扩展字段
    • response_metadataRecord<string, any> 类型,包含 token 使用量、完成原因等元数据

2.2 ChatMessageHistory:记忆的容器

InMemoryChatMessageHistory 是 LangChain 提供的内存级消息历史存储类,它是 Agent 短期记忆的核心载体。

2.2.1 内部数据结构
class InMemoryChatMessageHistory extends BaseChatMessageHistory {
  private messages: BaseMessage[];  // 核心存储:消息数组
}
属性名数据类型说明
messagesBaseMessage[]消息数组,按时间顺序存储所有对话消息
  • 数据结构本质:一个 BaseMessage 类型的数组(Array)
  • 存储顺序:按对话发生的时间顺序线性存储
  • 内存特性:存储在进程内存中,进程结束即丢失
2.2.2 核心方法签名
方法名参数类型返回值类型作用
addMessage(message)BaseMessagePromise<void>向记忆中追加一条消息
addMessages(messages)BaseMessage[]Promise<void>批量追加多条消息
getMessages()Promise<BaseMessage[]>获取全部历史消息
clear()Promise<void>清空所有记忆

三、记忆的完整生命周期

3.1 初始化:创建记忆容器

const history = new InMemoryChatMessageHistory();

执行过程

  1. 调用构造函数 new InMemoryChatMessageHistory()
  2. 内部初始化一个空数组 messages = []
  3. 返回一个 InMemoryChatMessageHistory 实例

此时的内存状态

history: InMemoryChatMessageHistory {
  messages: []  // length = 0
}

3.2 写入:将消息存入记忆

const userMessage1 = new HumanMessage("你今天吃什么?");
await history.addMessage(userMessage1);

addMessage 的执行流程

  1. 参数校验:检查传入的 message 是否为 BaseMessage 类型
  2. 追加操作:执行 this.messages.push(message)
  3. 异步返回:返回 Promise<void>(内存操作为同步,但接口设计为异步以兼容持久化实现)

写入后的数据结构

history.messages = [
  HumanMessage {
    content: "你今天吃什么?",
    type: "human",
    name: undefined,
    additional_kwargs: {}
  }
]
// length = 1

3.3 读取:从记忆中检索上下文

const messages1 = [systemMessage, ...(await history.getMessages())];

getMessages 的执行流程

  1. 返回 Promise.resolve(this.messages)
  2. 使用展开运算符 ... 将数组展开
  3. systemMessage 组合成新的消息数组

组合后的数据结构

messages1: BaseMessage[] = [
  SystemMessage { content: "...", type: "system" },
  HumanMessage { content: "你今天吃什么?", type: "human" }
]
// length = 2

3.4 模型调用:记忆 + 推理

const response1 = await model.invoke(messages1);

model.invoke() 接收 BaseMessage[] 类型的输入,返回 AIMessage 类型的输出。这一步中,记忆中的消息作为上下文被送入大模型进行推理。

3.5 更新:将回复写回记忆

await history.addMessage(response1);

模型的回复同样需要被写入记忆,以便下一轮对话时作为上下文使用。

更新后的数据状态

history.messages = [
  HumanMessage { content: "你今天吃什么?", type: "human" },
  AIMessage { content: "哈哈,今天我推荐...", type: "ai" }
]
// length = 2

四、完整示例的数据流转分析

以代码中的两轮对话为例,我们来追踪记忆容器中数据的完整变化过程。

4.1 初始状态

阶段history.messages 长度内容类型
初始化完成0[]

4.2 第一轮对话

// 步骤1:用户消息写入
await history.addMessage(userMessage1);  // length: 0 → 1

// 步骤2:读取 + 系统消息拼接
const messages1 = [systemMessage, ...(await history.getMessages())];
// messages1.length = 2 (system + human)

// 步骤3:模型推理
const response1 = await model.invoke(messages1);
// response1 类型: AIMessage

// 步骤4:回复写入记忆
await history.addMessage(response1);  // length: 1 → 2

第一轮结束后记忆状态

history.messages: BaseMessage[] (length = 2)
├─ [0] HumanMessage  "你今天吃什么?"
└─ [1] AIMessage     "哈哈,今天我推荐..."

4.3 第二轮对话

// 步骤1:用户消息写入
await history.addMessage(userMessage2);  // length: 2 → 3

// 步骤2:读取 + 系统消息拼接
const message2 = [systemMessage, ...(await history.getMessages())];
// message2.length = 4 (system + human + ai + human)

// 步骤3:模型推理
const response2 = await model.invoke(message2);

// 步骤4:回复写入记忆
await history.addMessage(response2);  // length: 3 → 4

第二轮结束后记忆状态

history.messages: BaseMessage[] (length = 4)
├─ [0] HumanMessage  "你今天吃什么?"
├─ [1] AIMessage     "哈哈,今天我推荐..."
├─ [2] HumanMessage  "推荐一份美食?"
└─ [3] AIMessage     "我推荐番茄炒蛋..."

4.4 遍历输出

const allMessages = await history.getMessages();
allMessages.forEach((msg, index) => {
    const type = msg.type;                    // string
    const prefix = type === 'human' ? '用户' : '助手';  // string
    console.log(`${index+1}.[${prefix}]:${msg.content.substring(0,50)}...`);
});

变量类型分析

变量类型说明
allMessagesBaseMessage[]完整的消息数组
msgBaseMessage当前遍历的消息对象
indexnumber数组索引,从 0 开始
typestring消息类型字符串
prefixstring三元运算符结果,'用户''助手'

五、内存级记忆的优缺点

5.1 优点

特性说明数据类型视角
读写速度快直接操作内存数组,无 I/O 开销Array.push() 时间复杂度 O(1)
实现简单基于原生数组即可实现无需外部依赖数据库
类型安全运行时保留完整的类实例信息instanceof 检查可用

5.2 缺点

特性说明数据类型视角
易失性进程重启后数据全部丢失内存(RAM)的固有特性
容量受限受限于进程可用内存BaseMessage[] 无限增长会导致 OOM
无法共享单进程独占,多实例间无法共享内存空间隔离
无持久化对话历史无法保存没有序列化到磁盘的机制

六、记忆系统的扩展方向

基于内存级记忆的局限性,实际生产环境中通常会引入更复杂的记忆架构:

6.1 持久化记忆

InMemoryChatMessageHistory 替换为持久化实现,如:

  • RedisChatMessageHistory — 基于 Redis,类型为 string 的 JSON 序列化存储
  • PostgresChatMessageHistory — 基于关系型数据库,表结构包含 idsession_idmessage(JSONB 类型)
  • FileChatHistory — 基于本地文件,通常为 JSON 格式

6.2 分层记忆架构

记忆层级存储介质数据结构访问速度容量
短期记忆内存BaseMessage[]极快
中期记忆Redis / 数据库序列化后的消息列表
长期记忆向量数据库Embedding[] + 原始文本

6.3 记忆的修剪与摘要

当消息数组过长时,需要进行记忆管理:

  • 滑动窗口:只保留最近 N 条消息(messages.slice(-N)
  • 摘要压缩:用 AI 总结历史对话,用一条摘要消息替换多条原始消息
  • 向量检索:将历史消息向量化存储,按需检索相关片段

七、总结

Agent 的记忆系统本质上是一个消息对象数组的生命周期管理过程。从最基础的 InMemoryChatMessageHistory 入手,我们可以看到:

  1. 记忆的基本单元是消息对象,每条消息有明确的类型(system / human / ai)和数据结构
  2. 记忆容器的核心是数组,通过 addMessagegetMessages 实现读写
  3. 每一轮对话都遵循「读取 → 拼接 → 推理 → 写入」的固定流程
  4. 内存级记忆简单高效但有局限,生产环境需要持久化和分层架构

理解了这些基础的数据类型和流转机制,就能更好地设计和优化 Agent 的记忆系统,让智能体拥有更"聪明"的记忆能力。