第7章:通过 Memory 记住对话
在构建对话式AI应用时,一个非常重要的功能就是让AI能够"记住"之前的对话内容,这样就能实现更加自然和连贯的对话体验。在LangChainGo中,这个功能是通过Memory(记忆)模块来实现的。本文将详细介绍Memory的工作原理和四种常见的Memory类型,即使是初学者也能轻松理解。
什么是Memory(记忆)?
Memory是LangChainGo中的一个核心组件,它允许应用程序记住和使用过去的交互信息。简单来说,它就是AI的"短期记忆",让AI在多轮对话中能够保持上下文连贯性。
没有Memory的情况下,每次与AI交互都是独立的,AI无法记住之前的对话内容。有了Memory后,AI可以将历史对话信息存储起来,并在后续对话中使用这些信息,从而实现更自然的对话体验。
Memory的四种主要类型
LangChainGo提供了多种Memory类型,每种都有其特定的用途和优势。下面我们来详细介绍四种最常见的Memory类型:
1. ConversationBufferMemory(对话缓存记忆)
这是最基础也是最直接的Memory类型。它会完整地保存所有的对话历史,无论是用户的输入还是AI的回复。
工作原理:
- 将每次对话的输入和输出都完整地存储在内存中
- 在下一次对话时,将完整的对话历史作为上下文传递给AI
优点:
- 实现简单,容易理解
- 保留所有对话细节
缺点:
- 随着对话轮数增加,占用的token数量线性增长
- 可能超出模型的上下文长度限制
使用示例:
memory := memory.NewConversationBuffer()
conversation := chains.NewConversation(llm, memory)
2. ConversationWindowBufferMemory(对话窗口缓存记忆)
这种Memory类型只保存最近几轮的对话,而不是全部对话历史。
工作原理:
- 设置一个窗口大小k,只保留最近k轮对话
- 当对话超过窗口大小时,自动移除最旧的对话记录
优点:
- 控制token使用量,避免超出上下文限制
- 适合只需要关注最近对话内容的场景
缺点:
- 会完全遗忘窗口外的对话内容
- 可能丢失重要的历史信息
使用示例:
// 只保留最近3轮对话
memory := memory.NewConversationWindowBuffer(3)
conversation := chains.NewConversation(llm, memory)
3. ConversationTokenBufferMemory(对话令牌缓存记忆)
这种Memory类型基于token数量来管理对话历史,而不是基于对话轮数。
工作原理:
- 设置一个最大token限制
- 当对话历史超过这个限制时,自动从最早的对话开始删除,直到满足token限制
优点:
- 精确控制token使用量
- 更好地适应不同长度的对话内容
缺点:
- 需要计算每条消息的token数量
- 可能丢失早期的重要信息
使用示例:
// 设置最大token限制为1000
memory := memory.NewConversationTokenBuffer(llm, 1000)
conversation := chains.NewConversation(llm, memory)
4. SimpleMemory(简单记忆)
这是一种不保存任何对话历史的Memory类型,主要用于不需要记忆功能的场景。
工作原理:
- 不保存任何对话历史
- 每次对话都是独立的
使用场景:
- 问答系统
- 不需要上下文的简单任务
使用示例:
memory := memory.NewSimple()
chain := chains.NewLLMChain(llm, prompt, chains.WithMemory(memory))
Memory底层调用原理
Memory的工作原理可以分为三个核心步骤:
1. 保存对话(SaveContext):
每当用户输入和AI输出生成后,Memory会调用SaveContext方法将这些信息保存起来。不同类型Memory的保存策略不同,比如ConversationBufferMemory会完整保存,而ConversationWindowBufferMemory只保存窗口内的对话。
2. 加载记忆(LoadMemoryVariables):
在下一次对话开始时,Memory会调用LoadMemoryVariables方法加载之前保存的对话历史,并将其作为上下文传递给AI模型。
3. 清理记忆(Clear):
当需要重置对话历史时,Memory会调用Clear方法清除所有保存的对话记录。
在LangChainGo中,这些操作都是通过实现schema.Memory接口来完成的,确保了不同类型Memory的一致性。
func main() { ctx := context.Background() llm := DoubaoOpenai() // 记忆的 c := chains.NewConversation(llm, memory.NewConversationBuffer()) //哇,你好呀超级赛亚人王子!那你现在是不是拥有超强的力量和酷炫的变身呢?能跟我讲讲成为超级赛亚人王子的独特体验吗?比如那种爆发力量时的热血沸腾,还有在战斗中展现出的超凡实力。你有没有经历过特别激烈的战斗,让你充分发挥出超级赛亚人的厉害之处呀? <nil> //你刚刚说自己是超级赛亚人王子呀,但你还没告诉我你的具体名字呢。你可以告诉我你的名字,这样我就能更准确地称呼你啦。 <nil> //你刚刚明确说自己是超级赛亚人王子呀,所以从你给我的信息来看,你是赛亚人王子呢。除非你现在是在跟我开玩笑或者想考考我,嘿嘿。你为什么突然这么问呀?难道有什么特别的原因让你怀疑自己的身份啦? <nil> // 普通的 //c := chains.NewLLMChain(llm, prompts.NewPromptTemplate("{{.input}}", []string{"input"})) //哇,你好呀超级赛亚人王子贝吉塔!以你的实力,在宇宙中一定所向披靡吧😎 最近又有什么新的战斗计划或者目标吗? <nil> //你还没有告诉我你的名字呢,所以我不知道你叫什么呀。至于“我是谁”这个问题,只有你自己最清楚啦,这取决于你的身份、经历、性格、价值观等等很多因素,要你自己去定义和探索呢。 你可以和我讲讲你自己,这样我能更好地了解你。 <nil> //哈哈,如果你说自己是赛亚人王子,那从现在起你就是啦😜 毕竟在想象的世界里,你可以拥有任何设定~你为什么突然想到自己是赛亚人王子呀🧐 是在看《龙珠》相关的内容吗? <nil> run, err := chains.Run(ctx, c, "你好,我是超级赛亚人王子") fmt.Println(run, err) s, err := chains.Run(ctx, c, "我是谁?我叫什么?") fmt.Println(s, err) s, err = chains.Run(ctx, c, "你确定我是赛亚人王子吗?") fmt.Println(s, err) }

浙公网安备 33010602011771号