序:Agent 的”失忆症”

你还记得三天前的午餐吃了什么吗?大概率不记得。

但如果我问你”你的名字是什么””你的技术栈偏好是什么””你的项目目录结构是怎样的”,你应该能立刻回答。因为这些信息已经进入了你的长期记忆

现在的 AI Agent 恰恰相反。它就像一个患有严重顺行性遗忘症的病人——每次对话都是全新开始,对话结束就忘得一干二净。

在上一篇文章中,我们构建了一个覆盖前后端的 AI 开发团队:PM Agent 拆解需求,前端和后端 Agent 并行编码,QA Agent 审查,DevOps Agent 部署。整套流程跑下来很顺畅,但有一个致命的问题:

每次你重新打开终端,Agent 都不认识你了。

它不记得你用的是 React 还是 Vue,不记得你偏好 pnpm 还是 npm,不记得上次写到一半的任务卡在哪里。更致命的是,当你构建一个客服 Agent个人助手时,它需要对用户的偏好、历史提问、项目上下文有持续的记忆。没有记忆的 Agent,就像一个每次都重新投简历的”新员工”。

记忆不是 Agent 的附加功能,它是 Agent 从”工具”进化到”伙伴”的核心基础设施。

本篇是《前端 + AI Agent 开发实战》系列的第四篇。我们将深入探讨 Agent 的记忆系统——从概念到架构,从代码到优化,让你构建的 Agent 真正”记住”该记住的东西。


一、记忆的分类体系:三层记忆模型

在认知科学中,人类记忆被分为感觉记忆 → 短时记忆 → 长时记忆。Agent 的记忆系统借鉴了同样的分层思想,但用计算机的术语重新定义了每一层:

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────┐
│ Agent 三层记忆模型 │
├─────────────┬─────────────┬─────────────────────┤
│ 短期记忆 │ 工作记忆 │ 长期记忆 │
│ Short-term │ Working │ Long-term │
│ Memory │ Memory │ Memory │
├─────────────┼─────────────┼─────────────────────┤
│ 上下文窗口 │ 会话级状态 │ 跨会话持久化 │
│ 128K tokens │ 任意大小 │ 无限扩展 │
│ 实时可用 │ 结构化存储 │ 需要检索 │
│ 对话结束丢失 │ 会话结束丢失 │ 永久存储 │
└─────────────┴─────────────┴─────────────────────┘

1.1 短期记忆:上下文窗口(Context Window)

这是最直接的记忆形式。LLM 的上下文窗口——GPT-4o 的 128K、Claude 的 200K——就是 Agent 的短期记忆

1
2
3
4
5
6
7
8
9
// 短期记忆的本质:消息数组
const shortTermMemory: Message[] = [
{ role: "system", content: "你是一个前端开发助手..." },
{ role: "user", content: "帮我创建一个登录页面" },
{ role: "assistant", content: "好的,我来创建...", tool_calls: [...] },
{ role: "tool", content: "{ file: 'Login.tsx', status: 'created' }" },
{ role: "user", content: "密码框需要加一个显示/隐藏按钮" },
// ... 所有对话历史都在这里
];

优势:所有信息即时可用,LLM 可以直接读取,无需检索。
劣势:窗口有限,满了就得丢掉旧内容;对话结束后全部丢失。

短期记忆就像你手机上的”最近应用”——方便快速切换,但数量有限,重启后就清空了。

1.2 工作记忆:会话级状态(Working Memory / Session State)

工作记忆是 Agent 在当前会话中维护的结构化状态。它不同于短期记忆——短期记忆是对话的”原文”,工作记忆是 Agent 从中提取出来的”要点”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 工作记忆:结构化的会话状态
interface WorkingMemory {
// 当前任务上下文
currentTask: {
description: string;
status: 'planning' | 'executing' | 'reviewing' | 'completed';
checklist: { item: string; done: boolean }[];
};

// 项目上下文
project: {
name: string;
techStack: string[];
directoryStructure: Record<string, string>;
activeFiles: string[];
};

// 用户偏好(当前会话)
preferences: {
language: 'TypeScript' | 'JavaScript';
framework: 'React' | 'Vue' | 'Next.js';
styling: 'Tailwind' | 'CSS Modules' | 'styled-components';
packageManager: 'pnpm' | 'npm' | 'yarn';
};

// 中间结果缓存
cache: {
lintResults?: LintResult;
buildOutput?: string;
testResults?: TestReport;
};
}

优势:结构化、可查询、可更新,不受上下文窗口限制。
劣势:会话结束后丢失,除非显式持久化。

1.3 长期记忆:跨会话持久化(Long-term Memory)

这是真正让 Agent 拥有”身份”和”经验”的记忆层。长期记忆分为两类:

类型 含义 示例 存储方式
语义记忆 (Semantic) 知识、事实、偏好 “用户偏好 React + TypeScript””项目使用 pnpm monorepo” 键值存储 / 结构化数据库
情节记忆 (Episodic) 历史对话、事件 “昨天用户修改了支付模块的 bug””上周 QA Agent 指出了3个常见错误” 向量数据库 / 全文搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 长期记忆的数据模型
interface LongTermMemory {
// 语义记忆:结构化知识
semantic: {
userPreferences: Record<string, any>;
projectConventions: Record<string, any>;
domainKnowledge: Record<string, any>;
};

// 情节记忆:历史事件
episodic: {
id: string;
timestamp: Date;
type: 'conversation' | 'action' | 'decision' | 'error';
summary: string;
embedding: number[]; // 向量,用于相似度检索
metadata: Record<string, any>;
}[];
}

类比理解:短期记忆是你正在看的这本书的这一页,工作记忆是你对本章内容的理解笔记,长期记忆是整本书以及你读过的所有其他书。


二、短期记忆管理:上下文窗口的艺术

短期记忆虽然是”最低级”的记忆形式,但它的管理却是最直接影响 Agent 输出质量的环节。因为 LLM 的输出质量与上下文中信息的相关性密度高度正相关。

2.1 上下文窗口的三大难题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 难题一:窗口溢出
// 128K 看起来很大,但一次工具调用的来回就能吃掉几千 token
const sessionStats = {
systemPrompt: 2000, // 系统提示词
userMessages: 5000, // 用户消息累计
assistantReplies: 8000, // AI 回复累计
toolCalls: 12000, // 工具调用+返回结果
toolResults: 60000, // 工具结果(如读取的大文件内容)
// 总计:87000 token,窗口已用掉 68%
};

// 难题二:中间遗忘(Lost in the Middle)
// LLM 对上下文开头和结尾的内容理解最好,中间部分容易被忽略
// 你给 Agent 设的编码规范,如果在第 5000 行,大概率会被"遗忘"

// 难题三:无关信息污染
// 前几轮的工具调用结果在当前轮已经没有用了,但还占着位置

2.2 上下文压缩策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 策略一:工具结果摘要化
// 不要直接往上下文塞 5000 行的终端输出,先做摘要
async function compressToolResult(
toolName: string,
rawResult: string,
maxTokens: number = 1000
): Promise<string> {
// 对过大的工具结果进行摘要
if (estimateTokens(rawResult) <= maxTokens) {
return rawResult;
}

const compressed = await summarizeLLM.call({
prompt: `压缩以下${toolName}的输出,保留关键信息:
- 错误信息(完整保留)
- 文件路径(完整保留)
- 数值数据(完整保留)
- 重复内容(只保留第一条)
- 日志噪音(删除)`,
input: rawResult,
});

return compressed;
}

// 策略二:滑动窗口 + 摘要("Summarize-and-Slide")
interface ContextWindowManager {
recentMessages: Message[]; // 最近 N 轮对话(完整保留)
summarizedHistory: string; // 更早的对话(摘要形式)
}

async function manageContextWindow(messages: Message[]): Promise<Message[]> {
const MAX_RECENT = 10; // 最近 10 轮完整保留
const MAX_TOKENS = 100_000;

if (estimateTokens(messages) <= MAX_TOKENS) {
return messages; // 窗口够用,不处理
}

// 分离近期和远期消息
const recent = messages.slice(-MAX_RECENT);
const old = messages.slice(0, -MAX_RECENT);

// 对远期消息做增量摘要
const summary = await summarizeConversation(old);

return [
{ role: "system", content: `[历史摘要]\n${summary}` },
...recent,
];
}

// 策略三:选择性遗忘(重要性评分)
// 不是所有消息都值得保留
function scoreMessageImportance(msg: Message): number {
let score = 0;

// 包含决策的消息 +5 分
if (msg.content.includes('决定') || msg.content.includes('选择')) score += 5;
// 包含错误信息的消息 +4 分
if (msg.content.includes('错误') || msg.content.includes('Error')) score += 4;
// 工具调用结果 +2 分
if (msg.role === 'tool') score += 2;
// 纯粹的确认回复 -1 分("好的""明白了")
if (msg.content.trim().length < 20) score -= 1;

return score;
}

2.3 Vercel AI SDK 的上下文管理

如果你使用的是 Vercel AI SDK(系列第一篇推荐的工具链),它其实内置了一些上下文管理机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { generateText, trimMessages } from 'ai';
import { openai } from '@ai-sdk/openai';

// AI SDK 的 trimMessages 会自动裁剪超长对话
const { text } = await generateText({
model: openai('gpt-4o'),
messages: trimMessages({
messages: conversationHistory,
maxTokens: 100_000, // 最大 token 数
strategy: 'last', // 保留最后的策略
allowSystemMessages: true, // 保留系统提示词
}),
});

trimMessages 的策略过于简单粗暴——直接截断。对于需要精细管理上下文的应用,需要自定义策略。


三、工作记忆:结构化会话状态

工作记忆是 Agent 在会话期间的”草稿纸”。短期记忆记录的是”对话原文”,工作记忆记录的是 Agent 从中提取和推断出的”结构化认知”。

3.1 工作记忆的核心职责

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 工作记忆管理器
class WorkingMemoryManager {
private state: WorkingMemory;
private updateHistory: StateUpdate[] = [];

// 职责一:状态读取——Agent 工具可随时查询状态
getTaskStatus(): TaskStatus {
return this.state.currentTask.status;
}

getUserPreferences(): Preferences {
return this.state.preferences;
}

// 职责二:状态更新——Agent 的行为可以修改状态
updateTaskChecklist(item: string, done: boolean): void {
const checklist = this.state.currentTask.checklist;
const target = checklist.find(c => c.item === item);
if (target) {
target.done = done;
this.recordUpdate('task_checklist', { item, done });
}
}

// 职责三:状态推导——从状态推断新的信息
inferProjectType(): 'frontend' | 'backend' | 'fullstack' {
const stack = this.state.project.techStack;
const hasFrontend = stack.some(t =>
['React', 'Vue', 'Angular', 'Next.js'].includes(t)
);
const hasBackend = stack.some(t =>
['Express', 'Fastify', 'NestJS'].includes(t)
);
if (hasFrontend && hasBackend) return 'fullstack';
if (hasFrontend) return 'frontend';
return 'backend';
}

// 职责四:跨工具协调——工具之间通过工作记忆传递数据
// 前端 Agent 的 build 工具可以读取后端 Agent 生成的 API 契约
getApiContract(): ApiContract | null {
return this.state.cache.apiContract ?? null;
}
}

3.2 LangGraph 的状态机:工作记忆的最佳实践

在前一篇中,我们使用了 LangGraph 的 Annotation.Root 作为多 Agent 系统的共享状态。这本质上就是工作记忆的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { Annotation, StateGraph } from '@langchain/langgraph';

// 这是 LangGraph 版的"工作记忆"
export const AgentState = Annotation.Root({
// === 基础状态 ===
messages: Annotation<BaseMessage[]>({
reducer: (current, incoming) => current.concat(incoming),
default: () => [],
}),

// === 任务状态 ===
currentTask: Annotation<string>,
taskProgress: Annotation<{
total: number;
completed: number;
current: string;
}>,

// === 项目上下文 ===
projectContext: Annotation<{
rootDir: string;
techStack: string[];
conventions: Record<string, string>;
}>,

// === 用户偏好 ===
userPreferences: Annotation<{
language: string;
framework: string;
packageManager: string;
}>,

// === 中间产物 ===
artifacts: Annotation<{
code: Record<string, string>; // 生成的代码文件
apiContracts: ApiContract[]; // API 接口定义
testResults: TestResult[]; // 测试结果
}>,

// === 决策日志 ===
decisions: Annotation<Decision[]>,
});

关键设计原则:LangGraph 的 reducer 定义了状态的合并策略messages 字段使用 concat reducer,新消息追加到末尾;而 userPreferences 使用默认 reducer(替换),新值完全覆盖旧值。

3.3 工作记忆的”遗忘”策略

工作记忆虽然理论上可以无限大,但全部加载到内存既不现实也不高效。你需要一个选择性遗忘策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 工作记忆压缩器
class WorkingMemoryCompressor {
// 规则一:已完成的任务详细过程可以压缩,只需保留结论
compressCompletedTasks(state: AgentState): void {
const completed = state.decisions.filter(d => d.status === 'completed');
if (completed.length > 5) {
// 只保留最近 5 个完整决策 + 最关键的 3 个历史决策
state.decisions = [
...completed.slice(0, 3), // 最关键的
...completed.slice(-5), // 最近的
];
}
}

// 规则二:成功的工具结果可以丢弃,失败的必须保留
filterToolResults(messages: BaseMessage[]): BaseMessage[] {
return messages.filter(msg => {
if (msg.getType() !== 'tool') return true; // 非工具结果保留
const content = typeof msg.content === 'string' ? msg.content : '';
// 失败的保留,成功的可以丢弃
return content.includes('error') || content.includes('失败');
});
}
}

四、长期记忆:跨越会话的”元认知”

这是本文的核心。让 Agent 记住这一会话之前发生的事情——这才是真正的”记忆”。

4.1 长期记忆的存储选型

1
2
3
4
5
6
7
8
9
10
11
存储需求分析:
┌──────────────────┬──────────────┬──────────────────┐
│ 记忆类型 │ 存储需求 │ 推荐方案 │
├──────────────────┼──────────────┼──────────────────┤
│ 用户偏好 │ 精确查询 │ PostgreSQL / KV │
│ 项目规范 │ 精确查询 │ PostgreSQL / KV │
│ 历史对话摘要 │ 语义检索 │ 向量数据库 │
│ 错误与修复记录 │ 语义检索 │ 向量数据库 │
│ 决策日志 │ 混合查询 │ PostgreSQL + 向量 │
│ 代码片段/模板 │ 语义检索 │ 向量数据库 │
└──────────────────┴──────────────┴──────────────────┘

为什么需要两种存储

语义记忆(偏好、规范)是精确的——用户用的就是 TypeScript,不是”类似 TypeScript 的语言”。这类数据用 PostgreSQL 或 KV 存储,直接 key-value 查询。

情节记忆(对话、事件)是模糊的——“上次那个支付模块的 bug 是怎么修来着?”你需要找到语义上最接近的历史记录,而不是精确匹配某个关键词。这类数据用向量数据库存储,通过 embedding 相似度检索。

4.2 长期记忆的基础实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// src/memory/long-term-memory.ts
import { embed } from 'ai';
import { openai } from '@ai-sdk/openai';
import pg from 'pg';

// ============ 第一部分:结构化语义记忆 ============

// 使用 PostgreSQL 存储确定性的偏好和知识
class SemanticMemoryStore {
constructor(private db: pg.Pool) {}

// 设置用户偏好
async setPreference(userId: string, key: string, value: any): Promise<void> {
await this.db.query(`
INSERT INTO user_preferences (user_id, key, value, updated_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (user_id, key)
DO UPDATE SET value = $3, updated_at = NOW()
`, [userId, key, JSON.stringify(value)]);
}

// 批量获取用户偏好
async getPreferences(userId: string): Promise<Record<string, any>> {
const { rows } = await this.db.query(
'SELECT key, value FROM user_preferences WHERE user_id = $1',
[userId]
);
return Object.fromEntries(rows.map(r => [r.key, r.value]));
}

// 设置项目规范
async setProjectConvention(
projectId: string,
category: string, // 'coding-style', 'naming', 'architecture' 等
rules: Record<string, string>
): Promise<void> {
await this.db.query(`
INSERT INTO project_conventions (project_id, category, rules, updated_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (project_id, category)
DO UPDATE SET rules = $3, updated_at = NOW()
`, [projectId, category, JSON.stringify(rules)]);
}
}

// ============ 第二部分:情节记忆(向量检索)============

interface EpisodicMemory {
id: string;
userId: string;
sessionId: string;
timestamp: Date;
type: 'conversation' | 'action' | 'decision' | 'error' | 'insight';
summary: string; // 人类可读的摘要
embedding: number[]; // 向量,用于语义检索
metadata: {
projectId?: string;
taskType?: string;
techStack?: string[];
outcome: 'success' | 'failure' | 'partial';
tags: string[];
};
}

class EpisodicMemoryStore {
constructor(
private db: pg.Pool, // PostgreSQL + pgvector
private embedModel = openai.embedding('text-embedding-3-small')
) {}

// 存储一段记忆
async store(memory: Omit<EpisodicMemory, 'embedding'>): Promise<void> {
// 1. 生成 embedding
const { embedding } = await embed({
model: this.embedModel,
value: `${memory.summary}\n${memory.metadata.tags.join(' ')}`,
});

// 2. 存储到 pgvector
await this.db.query(`
INSERT INTO episodic_memories
(id, user_id, session_id, timestamp, type, summary,
embedding, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7::vector, $8)
`, [
memory.id, memory.userId, memory.sessionId,
memory.timestamp, memory.type, memory.summary,
`[${embedding.join(',')}]`,
JSON.stringify(memory.metadata),
]);
}

// 语义检索:找到最相关的历史记忆
async recall(
query: string,
userId: string,
options?: {
limit?: number;
type?: EpisodicMemory['type'];
threshold?: number; // 相似度阈值
}
): Promise<EpisodicMemory[]> {
const { limit = 5, type, threshold = 0.7 } = options || {};

// 1. 生成查询向量
const { embedding } = await embed({
model: this.embedModel,
value: query,
});

// 2. 向量相似度检索(余弦距离)
const typeFilter = type
? `AND type = '${type}'`
: '';

const { rows } = await this.db.query(`
SELECT
id, user_id, session_id, timestamp, type,
summary, metadata,
1 - (embedding <=> $1::vector) AS similarity
FROM episodic_memories
WHERE user_id = $2
${typeFilter}
AND 1 - (embedding <=> $1::vector) > $3
ORDER BY embedding <=> $1::vector
LIMIT $4
`, [`[${embedding.join(',')}]`, userId, threshold, limit]);

return rows.map(row => ({
...row,
similarity: parseFloat(row.similarity),
}));
}

// 混合检索:语义 + 关键词 + 时间
async hybridRecall(
query: string,
userId: string,
filters: {
projectId?: string;
dateFrom?: Date;
dateTo?: Date;
outcome?: 'success' | 'failure';
limit?: number;
}
): Promise<EpisodicMemory[]> {
// 构建 WHERE 子句
const conditions: string[] = [`user_id = '${userId}'`];
if (filters.projectId) conditions.push(`metadata->>'projectId' = '${filters.projectId}'`);
if (filters.outcome) conditions.push(`metadata->>'outcome' = '${filters.outcome}'`);

const { embedding } = await embed({
model: this.embedModel,
value: query,
});

const { rows } = await this.db.query(`
SELECT *, 1 - (embedding <=> $1::vector) AS similarity
FROM episodic_memories
WHERE ${conditions.join(' AND ')}
ORDER BY embedding <=> $1::vector
LIMIT $2
`, [`[${embedding.join(',')}]`, filters.limit || 5]);

return rows;
}
}

4.3 记忆的生命周期管理

记忆不能无限制地堆积。每存储一条记忆都有成本(存储成本+检索噪音)。你需要记忆的生命周期管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// src/memory/memory-lifecycle.ts

class MemoryLifecycleManager {
// 1. 记忆冷却期(recency decay)
// 越久远的记忆,权重越低
calculateRelevanceScore(
memory: EpisodicMemory,
similarityScore: number
): number {
const ageInDays = (Date.now() - memory.timestamp.getTime()) / (1000 * 60 * 60 * 24);
const recencyWeight = Math.exp(-ageInDays / 30); // 30 天半衰期
return similarityScore * 0.7 + recencyWeight * 0.3;
}

// 2. 记忆合并(consolidation)
// 多条相似记忆合并为一条"经验"
async consolidate(episodes: EpisodicMemory[]): Promise<EpisodicMemory> {
const consolidated = await summarizeLLM.call({
prompt: '将以下多条相关记忆合并为一条经验总结,保留所有关键信息:',
input: episodes.map(e => `[${e.timestamp}] ${e.summary}`).join('\n'),
});

return {
id: `consolidated_${Date.now()}`,
summary: consolidated,
type: 'insight', // 合并后升级为 insight
metadata: {
sourceCount: episodes.length,
sourcePeriod: {
from: episodes[episodes.length - 1].timestamp,
to: episodes[0].timestamp,
},
outcome: 'partial', // 合并的记忆结果不一
tags: episodes.flatMap(e => e.metadata.tags),
},
} as EpisodicMemory;
}

// 3. 记忆淘汰(pruning)
// 自动删除低频、低质的记忆
async prune(userId: string): Promise<number> {
const { rowCount } = await this.db.query(`
DELETE FROM episodic_memories
WHERE user_id = $1
AND timestamp < NOW() - INTERVAL '90 days' -- 90 天前
AND type IN ('conversation', 'action') -- 对话和操作类
AND metadata->>'outcome' = 'success' -- 成功的记忆
LIMIT 1000
`, [userId]);

return rowCount || 0;
}
}

五、记忆系统架构设计

现在我们把三层记忆整合起来,设计一个完整的记忆系统。

5.1 整体架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌──────────────────────────────────────────────────────┐
│ Agent 记忆系统架构 │
├──────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ 短期记忆 │ │ 工作记忆 │ │ 长期记忆 │ │
│ │ (上下文) │ │ (会话状态) │ │ (持久化) │ │
│ │ │ │ │ │ │ │
│ │ messages[] │ │ AgentState │ │ PostgreSQL │ │
│ │ 128K tokens │ │ 结构化数据 │ │ + pgvector │ │
│ └─────┬───────┘ └──────┬──────┘ └──────┬───────┘ │
│ │ │ │ │
│ └────────┬────────┴────────┬────────┘ │
│ │ │ │
│ ┌──────▼─────────────────▼──────┐ │
│ │ 记忆编排层 │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ 提取 → 摘要 → 存储 │ │ │
│ │ │ 检索 → 注入 → 遗忘 │ │ │
│ │ └──────────────────────┘ │ │
│ └───────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────┘

5.2 记忆编排器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// src/memory/memory-orchestrator.ts

class MemoryOrchestrator {
constructor(
private shortTerm: ShortTermMemoryManager,
private working: WorkingMemoryManager,
private longTerm: {
semantic: SemanticMemoryStore;
episodic: EpisodicMemoryStore;
},
private lifecycle: MemoryLifecycleManager
) {}

// ============ 会话启动时 ============
async onSessionStart(userId: string, projectId?: string): Promise<SystemPrompt> {
// 1. 从长期记忆中加载用户偏好
const preferences = await this.longTerm.semantic.getPreferences(userId);

// 2. 从长期记忆中加载项目规范
const conventions = projectId
? await this.longTerm.semantic.getProjectConventions(projectId)
: {};

// 3. 检索最近的相关记忆
const recentMemories = await this.longTerm.episodic.recall(
projectId
? `项目 ${projectId} 的最近开发活动`
: '最近的开发活动和技术决策',
userId,
{ limit: 10, threshold: 0.6 }
);

// 4. 初始化工作记忆
this.working.init({
userPreferences: preferences,
projectContext: {
...conventions,
recentActivity: recentMemories.map(m => m.summary),
},
});

// 5. 构建增强的系统提示词
return this.buildSystemPrompt(preferences, conventions, recentMemories);
}

private buildSystemPrompt(
prefs: Record<string, any>,
conventions: Record<string, any>,
memories: EpisodicMemory[]
): SystemPrompt {
const memoryContext = memories.length > 0
? `\n\n## 历史上下文(最近相关记忆)\n${
memories.map(m =>
`- [${m.timestamp.toISOString().split('T')[0]}] ${m.summary}`
).join('\n')
}`
: '';

return {
role: 'system',
content: `
你是一个 AI 开发助手。

## 用户偏好
- 语言:${prefs.language || 'TypeScript'}
- 框架:${prefs.framework || 'React'}
- 包管理器:${prefs.packageManager || 'pnpm'}

## 项目规范
${Object.entries(conventions).map(([k, v]) => `- ${k}: ${v}`).join('\n')}
${memoryContext}

请遵循以上偏好和规范进行开发。
`.trim(),
};
}

// ============ 工具调用后:自动提取记忆 ============
async onToolCallComplete(
toolName: string,
result: ToolResult,
context: ActionContext
): Promise<void> {
// 判断这个工具调用是否值得记忆
if (!this.isMemorable(toolName, result)) return;

// 提取记忆摘要
const summary = await this.extractMemorySummary(toolName, result, context);

// 存储到情节记忆
await this.longTerm.episodic.store({
id: `mem_${Date.now()}`,
userId: context.userId,
sessionId: context.sessionId,
timestamp: new Date(),
type: this.classifyMemoryType(toolName, result),
summary,
metadata: {
projectId: context.projectId,
taskType: context.taskType,
outcome: result.success ? 'success' : 'failure',
tags: this.extractTags(toolName, context),
},
});
}

// 哪些工具调用值得记忆?
private isMemorable(toolName: string, result: ToolResult): boolean {
// 记住的:
const memorable = [
'create_file', // 创建了新文件
'edit_file', // 修改了代码
'run_command', // 执行了命令
'install_package', // 安装了依赖
'deploy', // 部署了
];

// 不记的:
const forgettable = [
'read_file', // 只是读文件(除非报错)
'list_files', // 只是列出文件
'get_status', // 只是查看状态
];

if (memorable.includes(toolName)) return true;
if (forgettable.includes(toolName) && result.success) return false;
// 失败的操作总是值得记住
return !result.success;
}

// ============ 会话结束时:整合记忆 ============
async onSessionEnd(sessionId: string): Promise<void> {
// 1. 这个会话的所有记忆
const sessionMemories = await this.longTerm.episodic.hybridRecall(
sessionId,
this.currentUserId,
{ limit: 50 }
);

// 2. 如果记忆较多,触发合并
if (sessionMemories.length > 20) {
const consolidated = await this.lifecycle.consolidate(sessionMemories);
await this.longTerm.episodic.store(consolidated as any);
}

// 3. 更新语义记忆(从中学习到的偏好和规范)
await this.updateSemanticMemory(sessionMemories);

// 4. 清理短期和工作记忆
this.shortTerm.clear();
this.working.clear();
}
}

5.3 记忆注入时机

记忆不是在”用户问了才去查”——好的记忆系统是主动的。在以下时机自动注入记忆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 记忆注入的触发点
const INJECTION_TRIGGERS = {
// 时机一:用户提出问题 → 检索相关知识
onUserMessage: async (message: string, userId: string) => {
return memoryOrchestrator.search(message, userId);
},

// 时机二:Agent 遇到错误 → 检索类似错误的修复记录
onError: async (error: Error, context: any) => {
return memoryOrchestrator.recall(
`错误:${error.message}`,
context.userId,
{ type: 'error', limit: 3 }
);
},

// 时机三:Agent 准备做决策 → 检索历史决策
onDecision: async (decision: string, userId: string) => {
return memoryOrchestrator.recall(
decision,
userId,
{ type: 'decision', limit: 5 }
);
},

// 时机四:Agent 准备写代码 → 检索项目规范和代码风格
onCodeGeneration: async (filePath: string, userId: string) => {
return memoryOrchestrator.recall(
`项目代码风格和 ${path.extname(filePath)} 文件的编写规范`,
userId,
{ limit: 5 }
);
},
};

六、LangGraph 记忆机制深入

如果你在上一篇中使用了 LangGraph 来编排多 Agent 系统,那么你很幸运——LangGraph 有内置的记忆持久化机制。

6.1 MemorySaver:会话内记忆

MemorySaver 是 LangGraph 最简单的 checkpoint 机制。它在内存中保存状态的快照,会话内可用,进程重启后丢失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { MemorySaver } from '@langchain/langgraph';

// 前一篇的代码,只需加一行
const checkpointer = new MemorySaver();

const workflow = new StateGraph({ channels: AgentState.spec })
// ... 添加节点和边 ...
.compile({
checkpointer, // ← 就这一行
});

// 使用时,需要提供 thread_id(会话 ID)
const config = { configurable: { thread_id: 'user-session-123' } };

// 第一次调用
await graph.invoke({ userRequirement: '创建一个登录页面' }, config);

// 同一个 thread_id 的第二次调用——Agent 记得上次的状态!
await graph.invoke({ userRequirement: '给登录页面加个记住密码功能' }, config);

MemorySaver 的核心价值在于断点续跑

1
2
3
4
5
6
// 如果 Agent 在一个长任务中卡住了(比如超时)
// 用同一个 thread_id 重新调用,它会从上次的 checkpoint 继续
const resumed = await graph.invoke(
{ userRequirement: '继续之前的工作' },
{ configurable: { thread_id: 'user-session-123' } }
);

6.2 PostgresSaver:跨会话持久化

PostgresSaver 把 checkpoint 持久化到 PostgreSQL。这意味着——
Agent 关掉重启后依然记得对话状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';

// 连接到 PostgreSQL
const checkpointer = await PostgresSaver.fromConnString(
'postgresql://user:password@localhost:5432/agent_db'
);

// 初始化表
await checkpointer.setup();

// 用法完全一样
const workflow = new StateGraph({ channels: AgentState.spec })
.compile({ checkpointer });

// 即使进程重启,使用同一个 thread_id 也能恢复状态
const config = { configurable: { thread_id: 'project-dev-2026' } };
await graph.invoke({ userRequirement: '继续上周的开发任务' }, config);

6.3 LangGraph Store:原生的长期记忆 API

LangGraph 0.2+ 引入了 Store 接口,专门用于长期记忆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { InMemoryStore } from '@langchain/langgraph';

// 创建一个 store
const store = new InMemoryStore();

// 存储记忆(跨 thread 共享)
await store.put(
['users', 'user-1', 'preferences'], // namespace
'prefs', // key
{
language: 'TypeScript',
framework: 'React',
packageManager: 'pnpm',
preferredDB: 'PostgreSQL',
}
);

// Agent 可以在 tool 中访问 store
const getUserPreferences = tool(
async ({ userId }, config) => {
const prefs = await config.store?.get(
['users', userId, 'preferences'],
'prefs'
);
return prefs?.value || {};
},
{
name: 'get_user_preferences',
description: '获取用户的技术偏好',
schema: z.object({ userId: z.string() }),
}
);

Storenamespace 设计很巧妙——它天然支持多租户和多维度:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 用户维度的记忆
store.put(['users', userId, 'preferences'], 'tech', { ... });
store.put(['users', userId, 'preferences'], 'ui', { ... });

// 项目维度的记忆
store.put(['projects', projectId, 'conventions'], 'coding-style', { ... });
store.put(['projects', projectId, 'conventions'], 'architecture', { ... });

// 团队维度的记忆
store.put(['teams', teamId, 'knowledge'], 'onboarding', { ... });

// Agent 本身的学习记忆
store.put(['agents', 'frontend-agent', 'learned'], 'react-patterns', { ... });

6.4 将 LangGraph Store 与向量搜索结合

InMemoryStore 适合精确 key-value 查询,但它不支持语义检索。对于情节记忆的检索,你需要将 Store 和向量数据库结合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class LangGraphMemoryStore {
constructor(
private store: InMemoryStore, // 精确查询
private vectorDB: EpisodicMemoryStore, // 语义检索
) {}

// 统一的记忆接口
async remember(
namespace: string[],
key: string,
value: any,
options?: { semantic?: boolean }
): Promise<void> {
// 精确存储
await this.store.put(namespace, key, value);

// 如果标注为语义记忆,同时写入向量库
if (options?.semantic) {
await this.vectorDB.store({
id: `${namespace.join('/')}/${key}`,
summary: JSON.stringify(value).slice(0, 500),
metadata: { namespace, key },
} as any);
}
}

// 精确查询
async recall(namespace: string[], key: string): Promise<any> {
const item = await this.store.get(namespace, key);
return item?.value;
}

// 语义查询
async search(query: string, limit?: number): Promise<any[]> {
return this.vectorDB.recall(query, 'global', { limit });
}
}

七、实战:为多 Agent 团队添加记忆

现在我们把记忆系统集成到上一篇文章构建的 AI 开发团队中。

7.1 改造目标

将上一篇的”失忆”流水线改造为”有记忆”的智能团队:

改造前 改造后
每次新项目从零开始 PM Agent 记住历史项目的需求模式
前端 Agent 不记得之前的组件 自动检索相似的已有组件,复用而非重写
QA Agent 无历史参考 基于历史审查记录,优先检查高频错误
DevOps Agent 每次都从零写配置 基于历史配置模板快速生成

7.2 创建记忆感知的 Agent 工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// src/agents/memory-aware-agent-factory.ts

interface MemoryAwareAgentConfig {
role: 'pm' | 'frontend' | 'backend' | 'qa' | 'devops';
llm: BaseChatModel;
tools: StructuredTool[];
memory: LangGraphMemoryStore;
userId: string;
projectId: string;
}

async function createMemoryAwareAgent(
config: MemoryAwareAgentConfig
): Promise<Runnable> {
const { role, llm, tools, memory, userId, projectId } = config;

// 1. 检索该角色的历史经验
const roleExperiences = await memory.search(
`${role} Agent 的历史开发经验和最佳实践`,
5
);

// 2. 检索项目相关的历史记忆
const projectMemories = await memory.search(
`项目 ${projectId}${role} 相关历史工作`,
10
);

// 3. 检索用户偏好(从语义记忆)
const userPrefs = await memory.recall(
['users', userId, 'preferences'],
'tech'
);

// 4. 检索项目规范(从语义记忆)
const conventions = await memory.recall(
['projects', projectId, 'conventions'],
'all'
);

// 5. 构建增强的系统提示词
const enhancedSystemPrompt = buildRolePrompt(role, {
preferences: userPrefs,
conventions,
experiences: roleExperiences,
recentWork: projectMemories,
});

// 6. 添加记忆相关的工具
const memoryTools = createMemoryTools(memory, userId, projectId);
const allTools = [...tools, ...memoryTools];

// 7. 创建 Agent
return createAgent({
model: llm,
tools: allTools,
systemPrompt: enhancedSystemPrompt,
});
}

// 角色的记忆增强系统提示词
function buildRolePrompt(
role: string,
context: {
preferences: any;
conventions: any;
experiences: any[];
recentWork: any[];
}
): string {
const basePrompt = BASE_PROMPTS[role];

const memorySection = `
## 你的记忆

### 用户偏好
${JSON.stringify(context.preferences, null, 2)}

### 项目规范
${JSON.stringify(context.conventions, null, 2)}

### 历史经验
${context.experiences.map((e, i) => `${i + 1}. ${e.summary}`).join('\n')}

### 近期相关工作
${context.recentWork.map((w, i) => `${i + 1}. ${w.summary}`).join('\n')}

请在以上记忆的基础上进行工作。
- 如果用户的偏好已明确,不要再次询问
- 如果历史中有可复用的代码或方案,优先复用
- 如果历史中有类似的错误,避免重复
`.trim();

return `${basePrompt}\n\n${memorySection}`;
}

// 记忆相关的工具
function createMemoryTools(
memory: LangGraphMemoryStore,
userId: string,
projectId: string
): StructuredTool[] {
return [
tool(
async ({ query }) => {
const results = await memory.search(query, 5);
return results.map(r => r.summary).join('\n---\n');
},
{
name: 'search_memory',
description: '在历史记忆中搜索相关信息。当你需要了解历史做法、类似问题、过往决策时使用。',
schema: z.object({
query: z.string().describe('搜索关键词或问题描述'),
}),
}
),
tool(
async ({ key, value }) => {
await memory.remember(
['projects', projectId, 'knowledge'],
key,
value,
{ semantic: true }
);
return `已记住:${key}`;
},
{
name: 'remember_this',
description: '记住一条重要信息,供将来使用。当你发现重要的项目约定、用户偏好或解决方案时使用。',
schema: z.object({
key: z.string().describe('记忆的标识'),
value: z.string().describe('记忆的内容'),
}),
}
),
tool(
async ({ query }) => {
const results = await memory.search(query, 5);
if (results.length === 0) return '没有找到可复用的代码。';
return results.map(r =>
`**来源**:${r.summary}\n\`\`\`\n${r.metadata?.code || '代码已归档'}\n\`\`\``
).join('\n---\n');
},
{
name: 'find_similar_code',
description: '在历史代码中搜索类似的实现。当你准备写新功能时,先检查是否有可复用的代码。',
schema: z.object({
query: z.string().describe('描述你要实现的功能'),
}),
}
),
];
}

7.3 记忆感知的 QA Agent

QA Agent 是最能从记忆中受益的角色——它可以记住高频错误模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// src/agents/memory-aware-qa-agent.ts

const MEMORY_AWARE_QA_PROMPT = `
你是一个代码审查专家。除了常规的代码审查外,你还需要利用历史记忆提供更深度的审查。

## 审查要求
1. TypeScript 类型安全
2. 安全漏洞检查
3. 错误处理完整性
4. 性能问题
5. 代码规范一致性

## 历史错误模式
在审查时,请特别关注以下历史高频错误:
- 检查是否有记忆库中记录的类似错误
- 如果发现与历史错误模式匹配的代码,标记为 CRITICAL
- 在审查报告中引用历史记忆作为依据

## 记忆工具
- 使用 search_memory 搜索历史错误模式
- 使用 remember_this 记录本次发现的新错误模式
`;

// 增强的审查报告格式
interface EnhancedQAReport {
score: number;
issues: {
severity: 'critical' | 'major' | 'minor';
file: string;
line: number;
description: string;
historicalReference?: { // ← 新增:引用历史记忆
memoryId: string;
summary: string;
frequency: number; // 历史出现次数
};
}[];
memoryLearned: string[]; // ← 新增:本次学到的经验
suggestions: string[];
}

7.4 集成后的流水线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// src/orchestrator-memory-aware.ts

export function createMemoryAwareDevTeam(
memory: LangGraphMemoryStore,
userId: string,
projectId: string
) {
const workflow = new StateGraph({ channels: AgentState.spec })
// 新增:记忆加载节点(会话启动时执行)
.addNode('load_memory', async (state) => {
const context = await memory.hybridRecall(
`项目 ${projectId} 的开发上下文`,
userId,
{ limit: 15 }
);

return {
...state,
memoryContext: context.map(c => ({
id: c.id,
summary: c.summary,
relevance: c.similarity,
})),
};
})

// PM Agent:增强系统提示词中注入记忆上下文
.addNode('pm', async (state) => {
const agent = await createMemoryAwareAgent({
role: 'pm',
llm: new ChatOpenAI({ model: 'gpt-4o', temperature: 0.2 }),
tools: pmTools,
memory,
userId,
projectId,
});

// 在上文的基础上分解需求
// state.memoryContext 包含了历史相关记忆
const result = await agent.invoke({
requirement: state.userRequirement,
historicalPatterns: state.memoryContext,
});

return { ...state, taskBreakdown: result };
})

// ... 其他节点同理 ...
.addNode('save_memory', async (state) => {
// 会话结束时保存本次学习到的新记忆
await memory.batchRemember([
{ namespace: ['projects', projectId, 'decisions'], key: 'tech-choices', value: state.decisions },
{ namespace: ['projects', projectId, 'patterns'], key: 'api-patterns', value: state.apiContracts },
]);
return state;
})

// 边:开始时加载记忆,结束时保存记忆
.addEdge('load_memory', 'pm')
.addEdge('devops', 'save_memory')
.addEdge('save_memory', END)
.compile({
checkpointer: new PostgresSaver.fromConnString(process.env.DATABASE_URL!),
});
}

八、记忆压缩与检索优化

记忆系统面临一个核心矛盾:记得越多,检索越慢,噪音越大。如果不加控制,记忆系统最终会变成一个”什么都记得但什么都找不到”的信息沼泽。

8.1 分级记忆缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 三级缓存策略:热 → 温 → 冷
class TieredMemoryCache {
// L1:热缓存(内存,毫秒级)
// 存储当前会话的活跃记忆
private hotCache = new Map<string, { data: any; accessCount: number }>();

// L2:温缓存(Redis,毫秒~十毫秒级)
// 存储最近的会话记忆
private warmCache: Redis;

// L3:冷存储(PostgreSQL + pgvector,十毫秒~百毫秒级)
// 存储全部历史记忆
private coldStorage: EpisodicMemoryStore;

async recall(query: string, userId: string): Promise<EpisodicMemory[]> {
// 1. 先查热缓存
const hotKey = `${userId}:${query.slice(0, 50)}`;
if (this.hotCache.has(hotKey)) {
const cached = this.hotCache.get(hotKey)!;
cached.accessCount++;
return cached.data;
}

// 2. 再查温缓存
const warmResult = await this.warmCache.get(hotKey);
if (warmResult) {
const parsed = JSON.parse(warmResult);
this.hotCache.set(hotKey, { data: parsed, accessCount: 1 });
return parsed;
}

// 3. 最后查冷存储
const results = await this.coldStorage.recall(query, userId);

// 4. 写入热缓存和温缓存
this.hotCache.set(hotKey, { data: results, accessCount: 1 });
await this.warmCache.set(hotKey, JSON.stringify(results), 'EX', 3600);

return results;
}
}

8.2 增量式记忆摘要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 将原始对话"蒸馏"为分层摘要
async function distillConversation(
messages: Message[],
levels: number = 3 // 摘要层次数
): Promise<MemorySummary[]> {
const summaries: MemorySummary[] = [];

for (let level = 0; level < levels; level++) {
const chunkSize = Math.pow(4, level + 1); // 每层 4^n 条消息
const chunks = chunkArray(messages, chunkSize);

for (const chunk of chunks) {
const summary = await summarize(chunk, level);
summaries.push({
level,
summary,
sourceCount: chunk.length,
embedding: await embed(summary),
});
}
}

return summaries;
}

// 检索时:优先使用高层次的摘要,仅在需要细节时深入
async function retrieveWithLOD(
query: string,
summaries: MemorySummary[]
): Promise<string> {
// LOD = Level of Detail(细节层次)

// 第一步:用 L2(最高层摘要)做初筛
const l2Matches = semanticSearch(query, summaries.filter(s => s.level === 2));

if (l2Matches.length === 0) return '没有找到相关记忆';

// 第二步:如果初筛结果太多,用 L1 做精细化检索
if (l2Matches.length > 5) {
const l1Matches = semanticSearch(query, summaries.filter(s => s.level === 1));
return formatMemoryResult(l1Matches);
}

// 第三步:如果初筛结果精准,直接返回详情
return formatMemoryResult(l2Matches);
}

8.3 基于时间衰减的记忆权重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 艾宾浩斯遗忘曲线启发的记忆权重计算
function ebbinghausDecay(
memory: EpisodicMemory,
currentTime: number = Date.now()
): number {
const ageInHours = (currentTime - memory.timestamp.getTime()) / (1000 * 60 * 60);

// 艾宾浩斯遗忘曲线简化公式:R = e^(-t/S)
// S 是记忆强度,由记忆的重要性决定
const strength = calculateMemoryStrength(memory);
const retention = Math.exp(-ageInHours / strength);

// 其他调整因子
const accessBoost = Math.log(memory.metadata.accessCount + 1) * 0.1;
const outcomeMultiplier = memory.metadata.outcome === 'success' ? 1.0 : 1.5;
// 失败的记忆更值得保留(防止重复犯错)
const typeMultiplier = memory.type === 'insight' ? 2.0 : 1.0;

return Math.min(1, retention * outcomeMultiplier * typeMultiplier + accessBoost);
}

function calculateMemoryStrength(memory: EpisodicMemory): number {
// 基础 24 小时半衰期
let strength = 24;

// 包含决策 → 2x 半衰期
if (memory.type === 'decision') strength *= 2;
// insight 级别 → 4x 半衰期
if (memory.type === 'insight') strength *= 4;
// 有代码 → 1.5x 半衰期
if (memory.metadata?.code) strength *= 1.5;

return strength;
}

8.4 检索效率优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 向量检索的几个优化技巧
const optimizationStrategies = {
// 1. 分区检索:按 user_id + project_id 分区,避免全库扫描
partitionedSearch: `
CREATE INDEX ON episodic_memories
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100)
WHERE user_id = $1 AND project_id = $2;
`,

// 2. 近似检索:用 ivfflat / hnsw 索引替代暴力检索
approximateSearch: `
-- 先建立 HNSW 索引
CREATE INDEX ON episodic_memories
USING hnsw (embedding vector_cosine_ops);

-- 查询时用 ef_search 控制精度/速度的平衡
SET hnsw.ef_search = 100;
`,

// 3. 批量嵌入:一次调用嵌入多个文本
batchEmbed: async (texts: string[]) => {
return embedMany({
model: openai.embedding('text-embedding-3-small'),
values: texts,
});
},

// 4. 嵌入缓存:相同文本不重复计算嵌入向量
embedCache: new Map<string, number[]>(),

// 5. 后过滤:先做向量检索,再用结构化条件过滤
// ❌ 不要:WHERE metadata->>'outcome' = 'failure' AND embedding <=> ...
// ✅ 应该:先做向量检索取 top 100,再在应用层过滤 outcome
};

九、总结与未来展望

9.1 核心要点回顾

层次 是什么 怎么实现 生命周期
短期记忆 上下文窗口中的消息 消息数组管理 + 压缩 单次对话
工作记忆 会话中的结构化状态 LangGraph State / 自定义状态 单次会话
语义记忆 偏好、规范、知识 PostgreSQL / KV 存储 永久(手动更新)
情节记忆 历史对话、事件、决策 pgvector / 向量数据库 永久(自动衰减)

五个核心设计原则

  1. 分层存储:不要把所有记忆塞进上下文窗口。短期、工作、长期各司其职。
  2. 主动注入:记忆不是等用户问才查,而是在关键时机自动注入。
  3. 结构化+语义化双模式:偏好用精确查询,历史用语义检索。
  4. 生命周期管理:记忆需要冷却、合并、淘汰,否则会变成信息沼泽。
  5. LangGraph 加持:如果使用多 Agent 编排,LangGraph 的 Store + PostgresSaver 是最快上手的选择。

9.2 什么时候该用哪种记忆?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
用户说:"帮我创建一个登录页面"
→ 工作记忆:记录当前任务"创建登录页面"
→ 长期记忆-语义:检查用户偏好(React? Vue? CSS方案?)
→ 长期记忆-情节:搜索"登录页面"的历史实现

Agent 执行过程中:
→ 短期记忆:工具调用和返回结果
→ 工作记忆:任务进度(完成了哪些步骤)
→ 长期记忆-情节:记录关键决策和错误

会话结束后:
→ 工作记忆 → 情节记忆:摘要本次会话的关键信息
→ 情节记忆 → 语义记忆:如果有新的偏好或规范,更新语义记忆
→ 清理短期和工作记忆

9.3 从记忆到”认知”

记忆是 Agent 智能化的基础,但它不是终点。在记忆之上,还有更高层的认知能力:

1
2
3
4
5
6
7
8
9
记忆(Memory)

学习(Learning)—— 从记忆中发现模式

推理(Reasoning)—— 基于模式做判断

规划(Planning)—— 基于判断制定策略

元认知(Metacognition)—— 反思和优化自己的思考过程

9.4 系列文章路线图

本系列已发布和计划中的文章:

主题 状态
从 AI 补全到自主开发:前端工程师的范式跃迁 ✅ 已发布
Agent 工具设计模式:如何为你的项目定制工具集 ✅ 已发布
多 Agent 编排实战:构建 AI 开发团队 ✅ 已发布
Agent 的记忆与状态管理(本文) ✅ 已发布
Agent 的安全与权限控制 🔜 即将发布
从开发到生产:Agent 应用的部署与监控 📋 规划中

9.5 写在最后

如果你正在构建一个 Agent 应用,我建议你不要等到”需要记忆”时才去设计记忆系统。就像你不会等到用户数据丢失了才去设计数据库——记忆系统应该从一开始就作为 Agent 的基础设施来规划。

一个简单的起步建议:

  • 第一周:实现语义记忆(用户偏好 + 项目规范),用 PostgreSQL 存储
  • 第二周:加入情节记忆(历史对话摘要),用 pgvector 做语义检索
  • 第三周:优化生命周期管理(合并、衰减、淘汰)
  • 第四周:整合到 LangGraph 的多 Agent 流水线中

记忆不是 Agent 的”附加功能”。就像没有海马体的人类无法形成新的记忆一样,没有记忆系统的 Agent 永远无法成为真正的”智能体”。


本文所有代码可在真实环境中运行。如果你在实践过程中遇到问题,欢迎在评论区讨论。下一篇我们将深入探讨 Agent 的安全与权限控制——当 Agent 可以执行代码、读写文件、访问网络时,如何确保它不会成为安全漏洞的来源。