AI是否有记忆?
不知道大家在用AI应用的时候是否留意过,为什么不管什么应用都有一个添加新会话的按钮,例如
难道不能就一个对话框一直聊下去吗?
还有一个问题,为什么我在第一个会话跟他说的一些事情,换另外一个会话他就不知道了,但如果还在原来的会话里面,他就能知道?
上述的2个问题的本质都跟AI的记忆有关。
默认状态:无记忆的独立交互
大模型在默认情况下是无状态的,每次调用都是独立的。这意味着,如果不采取额外措施,模型无法记住之前的对话内容。例如,当你今天与模型讨论了一个话题,明天再次交互时,模型不会记得昨天的对话。
然而,像ChatGPT这样的系统能够实现上下文连贯性,这得益于其记忆机制。这种机制通过存储对话历史并将上下文作为提示的一部分传递给模型,使得模型能够基于之前的交互进行回应。
记忆机制的核心:ConversationChain
在LangChain等框架中,ConversationChain
是实现记忆机制的关键组件。它通过定义对话的格式和记忆机制紧密集成,为模型提供了上下文信息。
ConversationChain的工作原理
const { OpenAI } = require('@langchain/openai');
const { ConversationChain } = require('@langchain/chains');
const llm = new OpenAI({ temperature: 0.5, modelName: "gpt-3.5-turbo-instruct" });
const convChain = new ConversationChain({ llm });
console.log(convChain.prompt.template);
输出的提示模板如下:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.
Current conversation:
{history}
Human: {input}
AI:
这里的 {history}
存储了对话历史,而 {input}
是新输入的内容。通过将历史对话和新输入组合成提示,模型能够基于上下文进行回应。
记忆管理方法
为了有效管理记忆,LangChain 提供了多种记忆机制,每种机制都有其特点和适用场景。
1. 记住所有的聊天记录
ConversationBufferMemory
是最基础的记忆机制,它简单地存储所有对话历史。
const { ConversationBufferMemory } = require('@langchain/chains');
const conversation = new ConversationChain({
llm,
memory: new ConversationBufferMemory()
});
// 第一轮对话
const result1 = await conversation.run("我计划下个月去日本旅行,有什么推荐的地方吗?");
console.log("第一次对话后的记忆:", conversation.memory.buffer);
// 第二轮对话
const result2 = await conversation.run("我特别喜欢历史和文化,有没有适合的地方?");
console.log("第二次对话后的记忆:", conversation.memory.buffer);
// 第三轮对话
const result3 = await conversation.run("那我需要提前多久预订酒店呢?");
console.log("第三次对话后的记忆:", conversation.memory.buffer);
输出显示记忆中存储了完整的对话历史。然而,这种方法会随着对话轮次增加而消耗大量Token,可能导致上下文窗口溢出。
2. 记住最近几次的聊天记录
ConversationBufferWindowMemory
通过限制记忆窗口大小(k
参数)只保留最近的几轮对话,从而减少Token消耗。
const { ConversationBufferWindowMemory } = require('@langchain/chains');
const conversation = new ConversationChain({
llm,
memory: new ConversationBufferWindowMemory({ k: 1 })
});
// 第一轮对话
await conversation.run("我计划下个月去日本旅行,有什么推荐的地方吗?");
// 第二轮对话
await conversation.run("我特别喜欢历史和文化,有没有适合的地方?");
// 第三轮对话
const result = await conversation.run("那我需要提前多久预订酒店呢?");
console.log("第三轮对话后的记忆:", conversation.memory.buffer);
由于窗口大小为1,第三轮对话时模型只能记住第二轮的内容,导致无法准确回忆第一轮的细节。
3. 总结历史对话
ConversationSummaryMemory
通过LLM对对话历史进行总结,减少Token使用。
const { ConversationSummaryMemory } = require('@langchain/chains');
const conversation = new ConversationChain({
llm,
memory: new ConversationSummaryMemory({ llm })
});
// 第一轮对话
await conversation.run("我计划下个月去日本旅行,有什么推荐的地方吗?");
// 第二轮对话
await conversation.run("我特别喜欢历史和文化,有没有适合的地方?");
// 第三轮对话
const result = await conversation.run("那我需要提前多久预订酒店呢?");
console.log("对话总结:", conversation.memory.buffer);
总结后的对话历史被存储,减少了Token消耗,但依赖于LLM的总结能力。
4. 混合不同的记忆方法
ConversationSummaryBufferMemory
结合了总结和缓冲的特点,通过 maxTokenLimit
参数灵活管理记忆。
const { ConversationSummaryBufferMemory } = require('@langchain/chains');
const conversation = new ConversationChain({
llm,
memory: new ConversationSummaryBufferMemory({ llm, maxTokenLimit: 300 })
});
// 第一轮对话
await conversation.run("我计划下个月去日本旅行,有什么推荐的地方吗?");
// 第二轮对话
await conversation.run("我特别喜欢历史和文化,有没有适合的地方?");
// 第三轮对话
const result = await conversation.run("那我需要提前多久预订酒店呢?");
console.log("混合记忆结果:", conversation.memory.buffer);
当对话超出Token限制时,早期对话会被总结,而近期对话保留原始内容。
总结与比较
以下是四种记忆机制的比较:
记忆机制 | 特点 | 优点 | 缺点 |
---|---|---|---|
ConversationBufferMemory | 存储所有对话历史 | 实现简单,适合短对话 | Token消耗大,容易达到上下文窗口限制 |
ConversationBufferWindowMemory | 只保留最近k轮对话 | 减少Token消耗 | 会丢失早期对话信息 |
ConversationSummaryMemory | 对话历史总结 | 节省Token,适合长对话 | 依赖LLM总结能力,可能丢失细节 |
ConversationSummaryBufferMemory | 总结早期对话,保留近期对话 | 灵活管理Token,适合多轮对话 | 实现复杂,初期Token消耗较高 |
通过合理选择记忆机制,可以在Token效率和上下文保留之间找到平衡,从而优化大模型在多轮对话中的表现。
现在我们来回答前面的2个问题
第一个问题是为什么要有新会话?
主要是如果一个会话一直聊下去,记忆的信息不管采用什么记忆方式都会一直增加,直到超过大模型允许是输入token数量就无法工作。
第二个问题为什么在不同的会话里面记忆是不共享的?
主要是记忆按会话区分,同样也不可能无限制的记录。
最后留一个问题
,有没有可能跨会话也能记忆一些事情呢?这里涉及到长期记忆的设计,先挖个坑以后再填。
推荐阅读