使用langchain与你自己的数据对话(五):聊天机器人

之前我已经完成了使用langchain与你自己的数据对话的前四篇博客,还没有阅读这四篇博客的朋友可以先阅读一下:

  1. 使用langchain与你自己的数据对话(一):文档加载与切割
  2. 使用langchain与你自己的数据对话(二):向量存储与嵌入
  3. 使用langchain与你自己的数据对话(三):检索(Retrieval)
  4. 使用langchain与你自己的数据对话(四):问答(question answering) 

今天我们来继续讲解deepleaning.AI的在线课程“LangChain: Chat with Your Data”的第六门课:chat。

Langchain在实现与外部数据对话的功能时需要经历下面的5个阶段,它们分别是:Document Loading->Splitting->Storage->Retrieval->Output,如下图所示:

在前面的四篇博客中我们以及完成了这5给阶段所有的内容介绍,并在第四篇博客中我们还创建了RetrievalQA实现了对数据的问答功能,但是这里有一个小小的缺陷,那就是通过RetrievalQA实现的问答功能只能针对当前问题进行回答,它无法参考上下文来来回答问题,也就是说它没有记忆能力,无法实现连贯性聊。今天我们就来解决这个问题,我们会创建一个真正的个性化聊天机器人,它会学习用户提供的数据,并解答任何关于数据内容的问题,并且它具有记忆能力,能够实现真正的连贯性聊天。

在讨论聊天机器人之前之前,先让我们完成一些基础性工作,比如设置一下openai的api key:

import os
import openai
import sys
sys.path.append('../..')import panel as pn  # GUI
pn.extension()from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env fileopenai.api_key  = os.environ['OPENAI_API_KEY']

 先前内容回顾

之前我们介绍了Langchain在实现与外部数据对话的功能时需要经历下面的5个阶段,它们分别是:Document Loading->Splitting->Storage->Retrieval->Output。下面我们通过代码来简单实现一下这5个阶段的功能:

from langchain.vectorstores import Chroma
from langchain.embeddings.openai import OpenAIEmbeddings#加载本地向量数据库
persist_directory = 'docs/chroma/'
embedding = OpenAIEmbeddings()
vectordb = Chroma(persist_directory=persist_directory, embedding_function=embedding)#搜索与问题相关的文档
question = "What are major topics for this class?"
docs = vectordb.similarity_search(question,k=3)#查看搜索结果中的文档数量
len(docs)

 这里我们在向量数据库中搜索到3篇与问题相关的文档,下面我们查看一下这3篇文档:

docs

 下面我们来创建RetrievalQA,同时我们加入一个prompt的模板,在该prompt我们要求llm尽量用简洁的语言来回答问题,并且不能编造答案,最后我们还要求llm在答案的结语上加上“thanks for asking!”,通过这个prompt模板llm能给出简洁的格式化的答案:


from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate# Build prompt
template = """Use the following pieces of context to answer the question at the end. \
If you don't know the answer, just say that you don't know, don't try to make up an answer. \
Use three sentences maximum. Keep the answer as concise as possible. \
Always say "thanks for asking!" at the end of the answer. {context}
Question: {question}
Helpful Answer:"""
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context", "question"],template=template,)# Run chain
from langchain.chains import RetrievalQA
question = "Is probability a class topic?"
qa_chain = RetrievalQA.from_chain_type(llm=ChatOpenAI(temperature=0),retriever=vectordb.as_retriever(),return_source_documents=True,chain_type_kwargs={"prompt": QA_CHAIN_PROMPT})result = qa_chain({"query": question})
result["result"]

 ​​​​​

 这里我们看到RetrievalQA返回了一个很简洁的答案,并在最后附加了“thanks for asking!”,这符合我们对它的要求。

ConversationalRetrievalChain

到目前为止我们已经创建好了RetrievalQA,可以实现对数据内容的问答,不过这里会有一个问题,就是通过RetrievalQA创建的检索问答链,它没有记忆功能,它无法记住之前用户已经提出过问题,所以RetrievalQA不能实现连贯性的聊天问答。为了解决这个功能,我们可以通过创建ConversationalRetrievalChain,它会存储每次聊天的历史记录,当LLM在回答当前问题的时候都会参考历史聊天记录,这样就可以实现连贯性的聊天:

为了保存么此用户和LLM之间的聊天记录,我们需要创建一个ConversationBufferMemory组件,该组件会自动保存每一次用户和LLM之间对话记录。ConversationalRetrievalChain包含3给主要的参数:

  • llm: 语言模型,这里我们使用openai的“gpt-3.5-turbo”模型
  • retriever:检索器,这里我们由向量数据库来创建检索器
  • memory:记忆力组件,这里我们使用ConversationBufferMemory
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain#创建memory
memory = ConversationBufferMemory(memory_key="chat_history",return_messages=True
)#创建ConversationalRetrievalChain
qa = ConversationalRetrievalChain.from_llm(llm=ChatOpenAI(temperature=0),retriever=vectordb.as_retriever(),memory=memory
)

这里我们创建了ConversationalRetrievalChain的实例qa,接下来我们来实现连贯性的聊天,我们首先向LLM提出一个问题:概率是这门课的主题吗?

question1="概率是这门课的主题吗?"
result = qa({"question": question1})
print(result['answer'])

 接下来我们第二给问题:为什么需要先修课程呢?,这里需要说明的是该问题其实是衔接第一个问题的答案,如果我们的ConversationalRetrievalChain有记忆功能,那么它一定会知道这里的先修课程是指哪些课程,并且给出正确的回答:

question2 = "为什么需要先修课程呢?"
result = qa({"question": question2})
print(result['answer'])

 这里我们向LLM提出了2个问题,第一个问题是:概率是这门课的主题吗?我们知道,我们的向量数据库中存储的是吴恩达老师著名的机器学习课程cs229的课程讲义,因此课程中涉及到了一些概率的基础知识,那么接下来提出的第二给问题:为什么需要先修课程呢?该问题其实是衔接第一个问题的答案,要回答该问题必须要知道这里的先修课程是指哪些课程,因为LLM在回答第一个问题的时候已经明确告知用户概率是这门课的一个主题,那么概率也就是这门课的先修课程,这里我们看到ConversationalRetrievalChain在回答第二给问题的时候已经参考了之前的历史聊天记录,因此它给出了合理的答案。

创建聊天机器人

下面我们把Langchain在实现与外部数据对话的功能的5个阶段所有的内容整合起来,然后建一个真正意义上的聊天机器人,这里我们在jupyter notebook中使用panel组件来创建一个GUI的聊天对话界面:

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter, RecursiveCharacterTextSplitter
from langchain.vectorstores import DocArrayInMemorySearch
from langchain.document_loaders import TextLoader
from langchain.chains import RetrievalQA,  ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import TextLoader
from langchain.document_loaders import PyPDFLoader
import panel as pn
import paramdef load_db(file, chain_type, k):# load documentsloader = PyPDFLoader(file)documents = loader.load()# split documentstext_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)docs = text_splitter.split_documents(documents)# define embeddingembeddings = OpenAIEmbeddings()# create vector database from datadb = DocArrayInMemorySearch.from_documents(docs, embeddings)# define retrieverretriever = db.as_retriever(search_type="similarity", search_kwargs={"k": k})# create a chatbot chain. Memory is managed externally.qa = ConversationalRetrievalChain.from_llm(llm=ChatOpenAI(temperature=0), chain_type=chain_type, retriever=retriever, return_source_documents=True,return_generated_question=True,)return qa class cbfs(param.Parameterized):chat_history = param.List([])answer = param.String("")db_query  = param.String("")db_response = param.List([])def __init__(self,  **params):super(cbfs, self).__init__( **params)self.panels = []self.loaded_file = "docs/cs229_lectures/MachineLearning-Lecture01.pdf"self.qa = load_db(self.loaded_file,"stuff", 4)def call_load_db(self, count):if count == 0 or file_input.value is None:  # init or no file specified :return pn.pane.Markdown(f"Loaded File: {self.loaded_file}")else:file_input.save("temp.pdf")  # local copyself.loaded_file = file_input.filenamebutton_load.button_style="outline"self.qa = load_db("temp.pdf", "stuff", 4)button_load.button_style="solid"self.clr_history()return pn.pane.Markdown(f"Loaded File: {self.loaded_file}")def convchain(self, query):if not query:return pn.WidgetBox(pn.Row('User:', pn.pane.Markdown("", width=600)), scroll=True)result = self.qa({"question": query, "chat_history": self.chat_history})self.chat_history.extend([(query, result["answer"])])self.db_query = result["generated_question"]self.db_response = result["source_documents"]self.answer = result['answer'] self.panels.extend([pn.Row('User:', pn.pane.Markdown(query, width=600)),pn.Row('ChatBot:', pn.pane.Markdown(self.answer, width=600, style={'background-color': '#F6F6F6'}))])inp.value = ''  #clears loading indicator when clearedreturn pn.WidgetBox(*self.panels,scroll=True)@param.depends('db_query ', )def get_lquest(self):if not self.db_query :return pn.Column(pn.Row(pn.pane.Markdown(f"Last question to DB:", styles={'background-color': '#F6F6F6'})),pn.Row(pn.pane.Str("no DB accesses so far")))return pn.Column(pn.Row(pn.pane.Markdown(f"DB query:", styles={'background-color': '#F6F6F6'})),pn.pane.Str(self.db_query ))@param.depends('db_response', )def get_sources(self):if not self.db_response:return rlist=[pn.Row(pn.pane.Markdown(f"Result of DB lookup:", styles={'background-color': '#F6F6F6'}))]for doc in self.db_response:rlist.append(pn.Row(pn.pane.Str(doc)))return pn.WidgetBox(*rlist, width=600, scroll=True)@param.depends('convchain', 'clr_history') def get_chats(self):if not self.chat_history:return pn.WidgetBox(pn.Row(pn.pane.Str("No History Yet")), width=600, scroll=True)rlist=[pn.Row(pn.pane.Markdown(f"Current Chat History variable", styles={'background-color': '#F6F6F6'}))]for exchange in self.chat_history:rlist.append(pn.Row(pn.pane.Str(exchange)))return pn.WidgetBox(*rlist, width=600, scroll=True)def clr_history(self,count=0):self.chat_history = []return cb = cbfs()file_input = pn.widgets.FileInput(accept='.pdf')
button_load = pn.widgets.Button(name="Load DB", button_type='primary')
button_clearhistory = pn.widgets.Button(name="Clear History", button_type='warning')
button_clearhistory.on_click(cb.clr_history)
inp = pn.widgets.TextInput( placeholder='Enter text here…')bound_button_load = pn.bind(cb.call_load_db, button_load.param.clicks)
conversation = pn.bind(cb.convchain, inp) jpg_pane = pn.pane.Image( './img/convchain.jpg')tab1 = pn.Column(pn.Row(inp),pn.layout.Divider(),pn.panel(conversation,  loading_indicator=True, height=300),pn.layout.Divider(),
)
tab2= pn.Column(pn.panel(cb.get_lquest),pn.layout.Divider(),pn.panel(cb.get_sources ),
)
tab3= pn.Column(pn.panel(cb.get_chats),pn.layout.Divider(),
)
tab4=pn.Column(pn.Row( file_input, button_load, bound_button_load),pn.Row( button_clearhistory, pn.pane.Markdown("Clears chat history. Can use to start a new topic" )),pn.layout.Divider(),pn.Row(jpg_pane.clone(width=400))
)
dashboard = pn.Column(pn.Row(pn.pane.Markdown('# ChatWithYourData_Bot')),pn.Tabs(('Conversation', tab1), ('Database', tab2), ('Chat History', tab3),('Configure', tab4))
)#启动聊天应用程序
dashboard

 总结

 今天我们学习了如何开发一个具有记忆能力的个性化问答机器人,所谓个性化是指该机器人可以针对用户数据的内容进行问答,我们在实现该机器人时使用了ConversationalRetrievalChain组件,它是一个具有记忆能力的检索链,也是机器人的核心组件。希望今天的内容对大家有所帮助!

参考资料

Overview — Panel v1.2.1

Welcome to Param! — param v1.13.0

https://github.com/sophiamyang/tutorials-LangChain

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java反射概述

*** 反射机制* author JIANGJINGWEI* create 2020-05-11-22:20*/ public class ReflectionTest1 {//反射之前,对Person的操作Testpublic void test1(){//1.创建Person对象Person p1 new Person("kite",123);//2.调用其属性和方法p1.age 10;System.out.p…

Unity 实现字幕打字效果

Text文本打字效果,TextMeshPro可以对应参考,差距不大,改改参数名就能用。改脚本原本被我集成到其他的程序集中,现在已经分离。 效果 实现功能 1.能够设置每行能够容纳的字数和允许的冗余 2.打字效果 3.每行打完上移 4.开头进入&…

网络安全进阶学习第十二课——SQL手工注入3(Access数据库)

文章目录 注入流程:1、判断数据库类型2、判断表名3、判断列名4、判断列数1)判断显示位 5、判断数据长度6、爆破数据内容 注入流程: 判断数据库类型 ——> 判断表名 ——> 判断列名 ——> 判断列名长度 ——> 查出数据。 asp的网…

Java课题笔记~ AspectJ 对 AOP 的实现(掌握)

AspectJ 对 AOP 的实现(掌握) 对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支…

Object Map 的相互转换

学生业务对象定义&#xff1a;Student Student student new Student(); student.setId(1L); student.setName("令狐冲") student.setAge(10) 第一种&#xff1a;通过Alibaba Fastjson实现 pom.xml 文件依赖 <dependency><groupId>com.alibaba</g…

PHP 前后端分离,运行配置

H5 WEB目录:安装 yarn install、npm install &#xff08;依赖包&#xff09; 在电脑&#xff1a;安装nodejs Composer下载 &#xff1a;https://getcomposer.org/

UNIX基础知识:UNIX体系结构、登录、文件和目录、输入和输出、程序和进程、出错处理、用户标识、信号、时间值、系统调用和库函数

引言&#xff1a; 所有的操作系统都为运行在其上的程序提供服务&#xff0c;比如&#xff1a;执行新程序、打开文件、读写文件、分配存储区、获得系统当前时间等等 1. UNIX体系结构 从严格意义上来说&#xff0c;操作系统可被定义为一种软件&#xff0c;它控制计算机硬件资源&…

【C++】异常exception

文章目录 1. C语言中传统的处理错误方法2. C中的异常3. 异常的使用3.1 异常的抛出和捕获3.2 异常的重新抛出3.3 异常安全3.4 异常规范 4. 自定义异常体系5. 异常的优缺点 &#x1f4dd; 个人主页 &#xff1a;超人不会飞)&#x1f4d1; 本文收录专栏&#xff1a;《C的修行之路》…

解释器模式-自定义语言的实现

有时&#xff0c;我们希望输入一串字符串&#xff0c;然后计算机能够按照预先定义的文法规则来对这个字符串进行解释&#xff0c;从而实现相应的功能。 例如&#xff0c;我们想实现简单的加减法接收器&#xff0c;只需输入一个表达式&#xff0c;它就能计算出表达式结果。比如…

性能测试jmeter连接数据库jdbc(sql server举例)

一、下载第三方工具包驱动数据库 1. 因为JMeter本身没有提供链接数据库的功能&#xff0c;所以我们需要借助第三方的工具包来实现。 &#xff08;有这个jar包之后&#xff0c;jmeter可以发起jdbc请求&#xff0c;没有这个jar包&#xff0c;也有jdbc取样器&#xff0c;但不能发起…

Metashape和PhotoScan中文版软件下载安装地址

Metashape的点云生成功能 Metashape具有强大的点云生成功能&#xff0c;可以将图像转换为精确的三维点云数据。点云数据是进行三维建模和地形分析的重要基础。 在使用Metashape时&#xff0c;用户可以通过使用图像对齐功能生成点云数据。软件根据对齐后的图像生成稠密的点云&a…

SpringBoot系列---【三种启动传参方式的区别】

三种启动传参方式的区别 1.三种方式分别是什么? idea中经常看到下面三种启动传参方式 优先级 Program arguments > VM options > Environment variable > 系统默认值 2.参数说明 2.1、VM options VM options其实就是我们在程序中需要的运行时环境变量&#xff0c;它需…