LangChain大型语言模型(LLM)应用开发(二):Conversation Memory

news2024/11/16 19:28:39

 

LangChain是一个基于大语言模型(如ChatGPT)用于构建端到端语言模型应用的 Python 框架。它提供了一套工具、组件和接口,可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain 可以轻松管理与语言模型的交互,将多个组件链接在一起,以便在不同的应用程序中使用。

今天我们来学习DeepLearning.AI的在线课程:LangChain for LLM Application Development的第二门课:Memory,该门课程主要讲解几种和LLM交互时的内存记忆方法。一般情况下我们通过api的方式来访问openai的语言模型时,LLM是没有记忆能力的,也就是说LLM不能记住之前与用户对话的内容,要解决这个问题,我们必须每次与LLM对话时都必须将之前的所有对话内容全部输入给LLM,但这样也会增加程序的复杂性,同时也会增加经济成本,因为像ChatGPT这样的LLM是根据用户提交的数据内容的token数量来收费的,如果我们每次和LLM交互时提交的内容越多也就意味着token越多,那么就会产生越多的费用。这里Langchain提供了几种内存记忆组件可以帮助我们使用更高效和经济的方法来与LLM交互。

大纲

  • ConversationBufferMemory
  • ConversationBufferWindowMemory
  • ConversationTokenBufferMemory
  • ConversationSummaryMemory

ConversationBufferMemory

ConversationBufferMemory是一种最简单的记忆力组件,它会记住每次与LLM对话内容,并在下一轮对话时将历史对话记录全部传给LLM,这样LLM就会记住之前的对话内容,下面我们看一个例子:

import os

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file

import warnings
warnings.filterwarnings('ignore')
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory


llm = ChatOpenAI(temperature=0.0)
#定义ConversationBufferMemory记忆力组件
memory = ConversationBufferMemory()
conversation = ConversationChain(
    llm=llm, 
    memory = memory,#加入ConversationBufferMemory组件
    verbose=True
)

这里我们定义了一个llm, 该llm默认使用的是openai的"gpt-3.5-turbo"模型,同时我们温度参数temperature设置为0.0,这个很重要,因为温度参数temperature代表了LLM回答问题时候的随机性,取值范围是0-1之间,如果temperature越大,则LLM回答问题的随机性就会越大,这里我们将temperature设置为0,其目的是让LLM每次只选择概率最高的答案,从而避免产生随机答案。

conversation.predict(input="你好,我的名字叫王老六")

 

 这里我们看到,在与LLM交互时Langchain会产生一个prompt其中除了用户的输入的内容外,还有一段英语的前缀信息,这里前缀信息+用户信息构成了一个完整的prompt,下面我们进行第二轮对话:

conversation.predict(input="1+1等于几?")

 在langchain产生的第二轮对话的prompt中,我们看到除了前缀信息以为,还增加了历史对话记录。下面我们看第三轮对话:

conversation.predict(input="你还记得我叫什么名字吗?")

 

 第三轮对话时用户询问了LLM是否还记得用户的名字,LLM给出了正确的答案,这是因为第三轮对话的prompt中包含了历史所有的对话记录,所以LLM能够记住用户的名字。这里记住所有历史对话记录的能力就是由ConversationBufferMemory组件来实现的。

下面我们可以查看ConversationBufferMemory的一些内置方法:

print(memory.buffer)

memory.load_memory_variables({})

 

memory = ConversationBufferMemory()

memory.save_context({"input": "你好"}, 
                    {"output": "有啥事吗?"})

print(memory.buffer)

 

memory.load_memory_variables({})

memory.save_context({"input": "想找你聊天,可以吗?"}, 
                    {"output": "好的,没问题!"})
memory.load_memory_variables({})

 

 ConversationBufferWindowMemory

ConversationBufferWindowMemory组件与ConversationBufferMemory组件功能类似,只是ConversationBufferWindowMemory组件增加了一个窗口参数k, 因为之前的ConversationBufferMemory组件会在prompt中记录历史所有的聊天对话内容,而ConversationBufferWindowMemory组件只会记住最近的k轮对话内容,更早之前的对话讲话被抛弃而不保存在prompt中,下面我们看一个例子:

from langchain.memory import ConversationBufferWindowMemory

llm = ChatOpenAI(temperature=0.0)

#定义内存组件
memory = ConversationBufferWindowMemory(k=1)#k=1,意味着只能记住最后1轮对话内容
conversation = ConversationChain(
    llm=llm, 
    memory = memory, #添加记忆力组件
    verbose=True #展示中间结果
)

 这里我们将ConversationBufferWindowMemory对象的参数K设置为1,这意味着在prompt中只保留最近一轮的历史对话记录。

conversation.predict(input="你好,我是王老六。")

 

conversation.predict(input="1+1等于几?")

conversation.predict(input="你还记得我叫什么名字吗?")

 

 由于在第三轮对话的时候,prompt中只保留了上一轮对话的历史记录,且没有包含首轮对话记录,因此LLM并不记得在首轮对话时用户告诉LLM关于用户名字的信息,因此此时LLM无法给出用户的名字。下面我们再做一些简单的测试:

memory = ConversationBufferWindowMemory(k=1)

memory.save_context({"input": "Hi"},
                    {"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"},
                    {"output": "Cool"})

memory.load_memory_variables({})

 这里我们也可以看到,虽然我们给memory组件手动增加了两组对话记录,但是最终它只保存了一组对话记录,这是因为我们定义memory时设置了窗口参数k=1所导致的。

ConversationTokenBufferMemory

ConversationTokenBufferMemory组件的功能也是限制prompt中存储对话记录的数量,与ConversationBufferWindowMemory不同的是ConversationBufferWindowMemory组件是根据窗口参数K来限制对话条数,而ConversationTokenBufferMemory组件是根据token数量来限制prompt中的对话条数:

#!pip install tiktoken

from langchain.memory import ConversationTokenBufferMemory
from langchain.llms import OpenAI
llm = ChatOpenAI(temperature=0.0)


memory = ConversationTokenBufferMemory(llm=llm, max_token_limit=30)
memory.save_context({"input": "AI is what?!"},
                    {"output": "Amazing!"})
memory.save_context({"input": "Backpropagation is what?"},
                    {"output": "Beautiful!"})
memory.save_context({"input": "Chatbots are what?"}, 
                    {"output": "Charming!"})

上面我们在定义ConversationTokenBufferMemory时设置了参数max_token_limit的值为30,这意味着prompt中的历史对话数据的token数量不能超过30个token,接着我们给ConversationTokenBufferMemory组件手动增加了3轮对话记录,下面我们看看ConversationTokenBufferMemory组件最终能保存多少轮对话记录:

memory.load_memory_variables({})

 这里我们看到ConversationTokenBufferMemory组件保存了最近的30个token左右的对话记录,更早之前的对话记录已被丢弃,关于计算token的方法,不在本篇博客中说明。用户可以执行查阅相关资料。

ConversationSummaryMemory

ConversationSummaryMemory顾名思义会在prompt中保存历史对话记录的摘要,而不是完整的对话记录,下面我们来看一个例子:

from langchain.memory import ConversationSummaryBufferMemory
from langchain.chat_models import ChatOpenAI

#定义llm
llm = ChatOpenAI(temperature=0.0)

schedule ="""
上午 8 点与您的产品团队召开会议。\
您需要准备好幻灯片演示文稿。\
上午 9 点到中午 12 点有时间处理你的 LangChain 项目,\
这会进展得很快,因为 Langchain 是一个非常强大的工具。\
中午,在意大利餐厅与开车的顾客共进午餐\
距您一个多小时的路程,与您见面,了解人工智能的最新动态。\
请务必携带您的笔记本电脑来展示最新的LLM演示。\
"""
#定义ConversationSummaryBufferMemory组件
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)
memory.save_context({"input": "你好"}, {"output": "什么事?"})
memory.save_context({"input": "没啥事情, 有个小问题请教"},
                    {"output": "好的,请说"})
memory.save_context({"input": "今天的日程安排是什么?"}, 
                    {"output": f"{schedule}"})

memory.load_memory_variables({})

在上面我们定义了一个ConversationSummaryMemory组件,并且设置了max_token_limit为100,这意味着prompt中的历史对话摘要的长度不能超过100个token。然后我们模拟了一组聊天记录,从返回的结果上看,memory组件中保存的不再是完整的对话记录,而是一段原始对话的摘要。保留摘要的好处是即保存了原始对话的内容的主要含义,又节省了token。

conversation = ConversationChain(
    llm=llm, 
    memory = memory,
    verbose=True
)

conversation.predict(input="一个好的演示应该展示什么?")

 参考资料

https://learn.deeplearning.ai/langchain/lesson/3/memory

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/706028.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java9集合类新增功能

前言 Java8及Java9在集合Collection类中新增了一些很好用的新方法&#xff0c;能方便程序员更便捷的处理集合数据&#xff0c;本文对其中的一些方法进行总结 一. List 1.创建 // 传统方法List<String> list1 new ArrayList<>();list1.add("item1");li…

论文导读 | Operation ResearchManagement Science近期文章精选

推文作者&#xff1a;周梓渊 编者按 本期我们选取了最近来自Operation Research和Management Science的六篇文章以飨读者&#xff0c;前四篇文章来自OR&#xff0c;最后两篇文章来自MS&#xff1b;内容涉及多个方面&#xff0c;实现了方法论与具体应用实践的结合&#xff0c;例…

Node.js HTTP 模块的内存泄露问题

很久没有逛社区了&#xff0c;晚上回来看了一下最近的情况&#xff0c;突然看到一个内存泄露问题&#xff0c;作为一个 APM 开发者&#xff0c;自然想分析其中的原因。 问题 下面介绍一下具体的问题。看一下 demo。 const http require(http)async function main () {let i…

用NumPy,梯度下降的方法来解决线性回归

import matplotlib.pyplot as plt import numpy as npdef reckonCost(X,y,theta):my.shape[0]innernp.power( ( (Xtheta)-y.T ) , 2)return np.sum(inner) / (2*m)# 定义梯度下降函数 def gradient_descent(X, y, theta, alpha, num_iters):# m len(y) # 样本数量my.shape[0]…

Leetcode-每日一题【86.分隔链表】

题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4,3,2,5,2], x 3输…

【数据挖掘】时间序列教程【三】

2.7 平稳性 序列的平稳性 是一个关键属性&#xff0c;它允许我们应用许多时间序列分析的标准工具。 如果对于大小 n 和任何整数 ,观察发现 具有与 相同的联合分布&#xff0c;则时间序列是严格平稳的。 换句话说&#xff0c;从分布的角度来看&#xff0c;平稳时间序列对移位…

【嵌入式Qt开发入门】初识信号与槽

信号与槽&#xff08;Signal & Slot&#xff09;是 Qt 编程的基础&#xff0c;也是 Qt 的一大创新。因为有了信号与槽的编程机制&#xff0c;在 Qt 中处理界面各个组件的交互操作时变得更加直观和简单。 信号&#xff08;Signal&#xff09;就是在特定情况下被发射的事件&a…

【Linux】C++项目实战-高并发服务器详析

目录 多进程实现并发服务器多线程实现并发服务器BIO模型NIO模型I/O多路复用(I/O多路转接)select主旨思想图解原理函数解析代码举例select的缺点 poll函数解析代码示例 epoll&#xff08;最重要&#xff0c;请重点掌握&#xff09;函数解析代码举例epoll的两种工作模式 橙色 多…

GO语言使用最简单的UI方案govcl

接触go语言有一两年时间了。 之前用Qt和C#写过桌面程序&#xff0c;C#会被别人扒皮&#xff0c;极度不爽&#xff1b;Qt默认要带一堆dll&#xff0c;或者静态编译要自己弄或者找库&#xff0c;有的库还缺这缺那&#xff0c;很难编译成功。 如果C# winform可以编译成二进制原生…

Android 应用层 到 HAL 层

Android 应用层 到 HAL 层 1、相关知识点1.1 概要1.2 参考 2、拿SensorService举例2.1 Android Apps > Android Framework阶段2.2 Android Framework内部阶段2.2.1 frameworks/base2.2.2 frameworks/native 2.3 Android Framework > HAL 阶段2.3.1 旧版 HAL 1、相关知识点…

前段搜索框不请求接口隐藏数据

项目介绍&#xff1a;uview-ui 1.x的&#xff0c;并且使用语言切换功能&#xff08;i18n&#xff0c;hbuilder新建项目选择i18n项目&#xff09;&#xff0c;因为是h5项目&#xff0c;所以使用location.reload()进行刷新 效果图&#xff1a; 主要判断在 v-if“!keyword || i…

Git 之 reset --hard 回退/回滚到之前的版本代码后,后悔了,如何在恢复之后的版本的方法简单整理

Git 之 reset --hard 回退/回滚到之前的版本代码后&#xff0c;后悔了&#xff0c;如何在恢复之后的版本的方法简单整理 目录 Git 之 reset --hard 回退/回滚到之前的版本代码后&#xff0c;后悔了&#xff0c;如何在恢复之后的版本的方法简单整理 一、简单介绍 二、操作步骤…

Redis是什么?(详细安装步骤)

一、Redis简介&#x1f349; 背景 在Web应用发展的初期&#xff0c;那时关系型数据库受到了较为广泛的关注和应用&#xff0c;原因是因为那时候Web站点基本上访问和并发不高、交互也较少。而在后来&#xff0c;随着访问量的提升&#xff0c;使用关系型数据库的Web站点多多少少…

代码随想录二刷 day38 | 动态规划之 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯

day38 509. 斐波那契数1 确定dp数组以及下标的含义2 确定递推公式3 dp数组如何初始化4 确定遍历顺序5 举例推导dp数组 70. 爬楼梯1 确定dp数组以及下标的含义2 确定递推公式3 dp数组如何初始化4 确定遍历顺序5 举例推导dp数组 746. 使用最小花费爬楼梯1 确定dp数组以及下标的含…

Golang每日一练(leetDay0113) 奇偶链表、链表随机节点

目录 328. 奇偶链表 Odd Even Linked-list &#x1f31f;&#x1f31f; 382. 链表随机节点 Llinked-list Random Node &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日…

docker安装php GD库

故事是这样的&#xff1a; 公司采购了一套商城源码&#xff0c;使用的是 TP5&#xff0c;同事先行&#xff0c;用宝塔部署到生产环境&#xff0c;运行正常。后面我忙完手里的项目&#xff0c;也加入其中&#xff0c;我本地使用的是 docker 当我部署好开始运行时&#xff0c;发…

初学mybatis(三)ResultMap及分页

学习回顾&#xff1a;初学mybatis&#xff08;二&#xff09; 一、查询为null问题 要解决的问题&#xff1a;属性名和字段名不一致 环境&#xff1a;新建一个项目&#xff0c;将之前的项目拷贝过来 1、查看之前的数据库的字段名 2、Java中的实体类设计 public class User {pri…

Redis各数据类型操作命令

一、Redis数据类型及命令 &#xff08;一&#xff09;String 类别命令描述命令示例备注取/赋值操作赋值set key valueset lclkey lclvalue取值 get keyget lclkey取值并赋值getset key valuegetset lclkey1 lclvalue1获取原值&#xff0c;并设置新的值仅当不存在时赋值setnx k…

服务器解析漏洞与cms靶场搭建教程

文章目录 一、解析漏洞定义二、Kali安装docker并搭建DVWA靶场三、Win7 IIS7漏洞复现四、BEES靶场搭建五、CPMS靶场搭建六、SDCMS靶场搭建 一、解析漏洞定义 解析漏洞主要是一些特殊文件被Apache、IIS、Nginx等Web服务器在某种情况下解释成脚本文件格式并得以执行而产生的漏洞 …

The Company Requires Superficial StudyPHP 变量的使用 ③

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; PHP MYSQL &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…