记忆与存储策略

做出来能够调用toolsagent远远还不够,为了能够让用户基于之前的会话内容去回答用户新的问题,还需要做记忆管理。langchainjs通过historyAPI存储用户对话和llm回答,还通过存储策略对记忆进行管理,毕竟对话内容可以无穷无尽。


image-20260331213411766

短时记忆

InMemoryChatMessageHistory创建’记忆’,利用运行内存存储用户与llm对话内容。

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
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { InMemoryChatMessageHistory } from "@langchain/core/chat_history";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});

async function inMemoryDemo() {
const history = new InMemoryChatMessageHistory();
const systemMessage = new SystemMessage(
"你是一个友好、幽默的做菜助手,喜欢分享美食和烹饪技巧。"
);

console.log("[第一轮对话]");
const userMessage1 = new HumanMessage(
"你最拿手的菜是什么?"
);

await history.addMessage(userMessage1);

const messages1 = [systemMessage, ...(await history.getMessages())];
const response1 = await model.invoke(messages1);
await history.addMessage(response1);

console.log(`用户: ${userMessage1.content}`);
console.log(`助手: ${response1.content}\n`);

console.log("[第二轮对话 - 基于历史记录]");
const userMessage2 = new HumanMessage(
"好吃吗?"
);

await history.addMessage(userMessage2);

const messages2 = [systemMessage, ...(await history.getMessages())];
const response2 = await model.invoke(messages2);
await history.addMessage(response2);

console.log(`用户: ${userMessage2.content}`);
console.log(`助手: ${response2.content}\n`);

console.log("[历史消息记录]");
const allMessages = await history.getMessages();
console.log(`共保存了 ${allMessages.length} 条消息:`);
allMessages.forEach((msg, index) => {
const type = msg.type;
const prefix = type === 'human' ? '用户' : '助手';
console.log(`  ${index + 1}. [${prefix}]: ${msg.content.substring(0, 50)}...`);
});
}

inMemoryDemo().catch(console.error);

image-20260331195545223

长时记忆

agent做长时记忆,有两种方式,一种是借助向量数据库,一种直接存为文件形式。两者都是需要用的时候找到对应位置的记忆,调取并加入llmprompt。此文讲述利用文件存储的方式。

  1. 通过json文件的方式存储聊天消息历史记录。
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
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { FileSystemChatMessageHistory } from "@langchain/community/stores/message/file_system";
import { HumanMessage, AIMessage, SystemMessage } from "@langchain/core/messages";
import path from "node:path";
import chalk from "chalk";

const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});

async function fileHistory() {
const filePath = path.join(process.cwd(), "chat_history.json");
const sessionId = "user_session_001";

const systemMessage = new SystemMessage(
"你是一个友好的做菜助手,喜欢分享美食和烹饪技巧。"
);

console.log(chalk.blue("[第一轮对话]"));
const history = new FileSystemChatMessageHistory({
filePath: filePath,
sessionId: sessionId,
});

const userMessage1 = new HumanMessage(
"红烧肉怎么做"
);

await history.addMessage(userMessage1);

const messages1 = [systemMessage, ...(await history.getMessages())];
const response1 = await model.invoke(messages1);
await history.addMessage(response1);

console.log(chalk.green(`用户: ${userMessage1.content}`));
console.log(chalk.green(`助手: ${response1.content}`));
console.log(chalk.green(`✓ 对话已保存到文件: ${filePath}\n`));

console.log(chalk.blue("[第二轮对话 - 基于历史记录]"));
const userMessage2 = new HumanMessage(
"好吃吗?"
);

await history.addMessage(userMessage2);

const messages2 = [systemMessage, ...(await history.getMessages())];
const response2 = await model.invoke(messages2);
await history.addMessage(response2);

console.log(chalk.green(`用户: ${userMessage2.content}`));
console.log(chalk.green(`助手: ${response2.content}`));
console.log(chalk.green(`✓ 对话已保存到文件: ${filePath}\n`));

}

fileHistory().catch(console.error);

image-20260331203410200

  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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { FileSystemChatMessageHistory } from "@langchain/community/stores/message/file_system";
import { HumanMessage, AIMessage, SystemMessage } from "@langchain/core/messages";
import path from "node:path";
import chalk from "chalk";

const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});

async function fileHistory() {
const filePath = path.join(process.cwd(), "chat_history.json");
const sessionId = "user_session_001";

const systemMessage = new SystemMessage(
"你是一个友好的做菜助手,喜欢分享美食和烹饪技巧。"
);


const restoredHistory = new FileSystemChatMessageHistory({
filePath: filePath,
sessionId: sessionId,
});

const restoredMessages = await restoredHistory.getMessages();
console.log(`从文件恢复了 ${restoredMessages.length} 条历史消息:`);


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

console.log();

console.log("[第三轮对话]");
const userMessage3 = new HumanMessage(
"需要哪些食材?"
);
await restoredHistory.addMessage(userMessage3);

const messages3 = [systemMessage, ...(await restoredHistory.getMessages())];
const response3 = await model.invoke(messages3);
await restoredHistory.addMessage(response3);

console.log(`用户: ${userMessage3.content}`);
console.log(`助手: ${response3.content}`);
console.log(`✓ 对话已保存到文件\n`);

}

fileHistory().catch(console.error);

image-20260331205028385

记忆管理策略

可以分为截断、总结、检索三大招数。

  1. 截断即通过保留最近的几段对话,为后续问答提供依据。
  2. 总结则通过保留最近几段对话,而再之前的会话通过llm总结,存入history
  3. 检索则通过将数据通过嵌入模型返回向量化数据存入向量数据库后,在新的对话中进行向量余弦匹配最接近之前的对话,放到prompt中与llm进行交互。