第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)
}

 

posted @ 2025-09-24 11:41  phpwyl  阅读(21)  评论(0)    收藏  举报