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

一、什么是 Agent 的记忆

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

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


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

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

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

2.1.1 消息基类结构

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

| 属性名 | 数据类型 | 说明 |
|---------------------|-----------------------|--------------------------------------|----------|
| content | string | 消息的文本内容 |
| type | string | 消息类型标识,如 'human''system''ai' |
| name | `string | undefined` | 可选的发送者名称 |
| additional_kwargs | Record<string, any> | 额外的关键字参数,用于扩展功能 |

2.1.2 三种核心消息类型

1. SystemMessage(系统消息)

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

2. HumanMessage(人类消息)

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

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

  • 数据特征

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

3. AIMessage(AI 消息)

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

三、记忆的完整生命周期

3.1 初始化:创建记忆容器

ini 复制代码
const history = new InMemoryChatMessageHistory();

执行过程

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

此时的内存状态

ini 复制代码
history: InMemoryChatMessageHistory {
  messages: []  // length = 0
}

3.2 写入:将消息存入记忆

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

addMessage 的执行流程

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

写入后的数据结构

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

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

ini 复制代码
const messages1 = [systemMessage, ...(await history.getMessages())];

getMessages 的执行流程

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

组合后的数据结构

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

3.4 模型调用:记忆 + 推理

ini 复制代码
const response1 = await model.invoke(messages1);

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

3.5 更新:将回复写回记忆

csharp 复制代码
await history.addMessage(response1);

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

更新后的数据状态

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

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

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

4.1 初始状态

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

4.2 第一轮对话

csharp 复制代码
// 步骤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

第一轮结束后记忆状态

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

4.3 第二轮对话

csharp 复制代码
// 步骤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

第二轮结束后记忆状态

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

4.4 遍历输出

typescript 复制代码
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)}...`);
});

变量类型分析

变量 类型 说明
allMessages BaseMessage[] 完整的消息数组
msg BaseMessage 当前遍历的消息对象
index number 数组索引,从 0 开始
type string 消息类型字符串
prefix string 三元运算符结果,'用户''助手'

五、内存级记忆的优缺点

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 的记忆系统,让智能体拥有更"聪明"的记忆能力。

相关推荐
行者全栈架构师1 小时前
WorkBuddy 开放平台个人开发者接入实战:从零到 Agent 应用的完整路径
人工智能·架构·代码规范
Apache IoTDB1 小时前
清华大学软件学院王建民院长:工业时序数智库架构与数据价值魔方
架构
ShineWinsu1 小时前
对于 Vue 3:开发前需要掌握的 JavaScript 核心基础:从变量、数组方法到 Promise、Async/Await 与 ES Module 的解析
javascript·vue.js·elasticsearch
convee_ai1 小时前
Agent 执行为什么需要独立 Sandbox:拆解 Hullwork 的 gVisor 隔离
架构·kubernetes
FFZero11 小时前
[mpv架构] (4) demux 线程到底在“预读“什么?
c++·架构·音视频·多媒体
晴天161 小时前
Tsup:TypeScript库打包的“零配置极速方案”
前端·javascript·typescript
evans在进步1 小时前
Tomcat 整体架构与启动流程:从 Server 到 Servlet 加载
servlet·架构·tomcat
阡陌数智1 小时前
高可用LLM服务架构:多模型聚合系统并发管控、限流熔断与降级容错实战
架构
weixin199701080161 小时前
[特殊字符]《二手ERP × 闲鱼消息驱动架构:正向+逆向交易消息如何驱动WMS出库与回传》(附Python源码)
开发语言·python·架构