青训营X豆包MarsCode 技术训练营第六课 | 聊天机器人实战过程记录

180 阅读14分钟

1、项目需求

聊天机器人(Chatbot)是LLM和LangChain的核心用例之一,很多人学习大语言模型,学习LangChain,就是为了开发出更好的、更能理解用户意图的聊天机器人。聊天机器人的核心特征是,它们可以进行长时间的对话并访问用户想要了解的信息。

那么我们为易速鲜花市场部、销售部和客服部门的员工,以及易速鲜花的广大用户来开发一个聊天机器人。

聊天机器人设计过程中的核心组件包括:

  • 聊天模型:这是对话的基础,它更偏向于自然的对话风格。你可以参考LangChain相关文档中所支持的聊天模型的列表。尽管大模型(LLM)也可以应用于聊天机器人,但专用的聊天模型(Chat Model)更适合对话场景。
  • 提示模板:帮助你整合默认消息、用户输入、历史交互以及检索时需要的上下文。
  • 记忆:它允许机器人记住与用户之间的先前互动,增强对话连贯性。
  • 检索器:这是一个可选组件,特别适合那些需要提供特定领域知识的机器人。

整体来说,聊天机器人的关键在于其记忆和检索能力,记忆使聊天机器人能够记住过去的交互,而检索则为聊天机器人提供最新的、特定于领域的信息。

这个项目的具体技术实现步骤,这里简述一下。

第一步: 通过LangChain的ConversationChain,实现一个最基本的聊天对话工具。

第二步: 通过LangChain中的记忆功能,让这个聊天机器人能够记住用户之前所说的话。

第三步: 通过LangChain中的检索功能,整合易速鲜花的内部文档资料,让聊天机器人不仅能够基于自己的知识,还可以基于易速鲜花的业务流程,给出专业的回答。

第四步(可选): 通过LangChain中的数据库查询功能,让用户可以输入订单号来查询订单状态,或者看看有没有存货等等。

第五步: 在网络上部署及发布这个聊天机器人,供企业内部员工和易速鲜花用户使用。

在上面的 5 个步骤中,我们使用到了很多LangChain技术,包括提示工程、模型、链、代理、RAG、数据库检索等。

2、开发最基本的聊天机器人

首先我们用LangChain打造出一个最简单的聊天机器人。

# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = 'Your OpenAI Key'

# 导入所需的库和模块
from langchain.schema import HumanMessage, SystemMessage
from langchain.chat_models import ChatOpenAI

# 定义一个命令行聊天机器人的类
class CommandlineChatbot:
    # 在初始化时,设置花卉行家的角色并初始化聊天模型
    def __init__(self):
        self.chat = ChatOpenAI()
        self.messages = [SystemMessage(content="你是一个花卉行家。")]

    # 定义一个循环来持续与用户交互
    def chat_loop(self):
        print("Chatbot 已启动! 输入'exit'来退出程序。")
        while True:
            user_input = input("你: ")
            # 如果用户输入“exit”,则退出循环
            if user_input.lower() == 'exit':
                print("再见!")
                break
            # 将用户的输入添加到消息列表中,并获取机器人的响应
            self.messages.append(HumanMessage(content=user_input))
            response = self.chat(self.messages)
            print(f"Chatbot: {response.content}")

# 如果直接运行这个脚本,启动聊天机器人
if __name__ == "__main__":
    bot = CommandlineChatbot()
    bot.chat_loop()

运行程序后,你可以一直和这个Bot聊天,直到你聊够了,输入exit,它会和你说再见。

img

好的,一个简单的聊天机器人已经搭建好了,不过,这个聊天机器人没有记忆功能,它不会记得你之前说过的话。

下面,我们要通过记忆机制,把它改造成一个能记住话的Chatbot。

3、增加记忆机制

下面,我们来通过ConversationBufferMemory给Chatbot增加记忆。具体代码如下:

# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = 'Your OpenAI Key'

# 导入所需的库和模块
from langchain.schema import HumanMessage, SystemMessage
from langchain.memory import ConversationBufferMemory
from langchain.prompts import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.chains import LLMChain
from langchain.chat_models import ChatOpenAI

# 设置OpenAI API密钥
os.environ["OPENAI_API_KEY"] = 'Your OpenAI Key'  

# 带记忆的聊天机器人类
class ChatbotWithMemory:
    def __init__(self):

        # 初始化LLM
        self.llm = ChatOpenAI()

        # 初始化Prompt
        self.prompt = ChatPromptTemplate(
            messages=[
                SystemMessagePromptTemplate.from_template(
                    "你是一个花卉行家。你通常的回答不超过30字。"
                ),
                MessagesPlaceholder(variable_name="chat_history"),
                HumanMessagePromptTemplate.from_template("{question}")
            ]
        )
        
        # 初始化Memory
        self.memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
        
        # 初始化LLMChain with LLM, prompt and memory
        self.conversation = LLMChain(
            llm=self.llm,
            prompt=self.prompt,
            verbose=True,
            memory=self.memory
        )

    # 与机器人交互的函数
    def chat_loop(self):
        print("Chatbot 已启动! 输入'exit'来退出程序。")
        while True:
            user_input = input("你: ")
            if user_input.lower() == 'exit':
                print("再见!")
                break
            
            response = self.conversation({"question": user_input})
            print(f"Chatbot: {response['text']}")

if __name__ == "__main__":
    # 启动Chatbot
    bot = ChatbotWithMemory()
    bot.chat_loop()

程序的核心是ChatbotWithMemory类,这是一个带有记忆功能的聊天机器人类。在这个类的初始化函数中,定义了一个对话缓冲区记忆,它会跟踪对话历史。在LLMChain被创建时,就整合了LLM、提示和记忆,形成完整的对话链。

img

你看,我们的 Chatbot 成功地复述出了我好几轮之前传递给它的关键信息,也就是我的姐姐已经44岁了。她的推荐是基于这个原则来进行的。

4、增加检索机制

下面,继续增强 Chatbot 的功能,我们要把易速鲜花的内部文档信息嵌入到大模型的知识库中。让它成为一个拥有“易速鲜花”价值观的Super客服。

img

# 导入所需的库
import os
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Qdrant
from langchain.memory import ConversationSummaryMemory
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain
from langchain.document_loaders import PyPDFLoader
from langchain.document_loaders import Docx2txtLoader
from langchain.document_loaders import TextLoader

# 设置OpenAI API密钥
os.environ["OPENAI_API_KEY"] = 'Your OpenAI Key'  

# ChatBot类的实现-带检索功能
class ChatbotWithRetrieval:

    def __init__(self, dir):

        # 加载Documents
        base_dir = dir # 文档的存放目录
        documents = []
        for file in os.listdir(base_dir): 
            file_path = os.path.join(base_dir, file)
            if file.endswith('.pdf'):
                loader = PyPDFLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith('.docx') or file.endswith('.doc'):
                loader = Docx2txtLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith('.txt'):
                loader = TextLoader(file_path)
                documents.extend(loader.load())
        
        # 文本的分割
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=0)
        all_splits = text_splitter.split_documents(documents)
        
        # 向量数据库
        self.vectorstore = Qdrant.from_documents(
            documents=all_splits, # 以分块的文档
            embedding=OpenAIEmbeddings(), # 用OpenAI的Embedding Model做嵌入
            location=":memory:",  # in-memory 存储
            collection_name="my_documents",) # 指定collection_name
        
        # 初始化LLM
        self.llm = ChatOpenAI()
        
        # 初始化Memory
        self.memory = ConversationSummaryMemory(
            llm=self.llm, 
            memory_key="chat_history", 
            return_messages=True
            )
        
        # 设置Retrieval Chain
        retriever = self.vectorstore.as_retriever()
        self.qa = ConversationalRetrievalChain.from_llm(
            self.llm, 
            retriever=retriever, 
            memory=self.memory
            )

    # 交互对话的函数
    def chat_loop(self):
        print("Chatbot 已启动! 输入'exit'来退出程序。")
        while True:
            user_input = input("你: ")
            if user_input.lower() == 'exit':
                print("再见!")
                break
            # 调用 Retrieval Chain  
            response = self.qa(user_input)
            print(f"Chatbot: {response['answer']}")

if __name__ == "__main__":
    # 启动Chatbot
    folder = "OneFlower"
    bot = ChatbotWithRetrieval(folder)
    bot.chat_loop()

通过文档加载、文本分割、文档向量化以及检索功能,这个新的机器人除了常规的聊天功能,还能够检索存储在指定目录中的文档,并基于这些文档提供答案。

当用户输入一个问题时,机器人首先在向量数据库中查找与问题最相关的文本块。这是通过将用户问题转化为向量,并在数据库中查找最接近的文本块向量来实现的。然后,机器人使用 LLM(大模型)在这些相关的文本块上进一步寻找答案,并生成回答。

现在,新的Chatbot既能够回答一般性的问题,又能够回答易速鲜花内部问题,成了一个多面手!

img

5、项目部署

Flask是一个通用的、微型的Web应用框架,非常适合创建各种Web应用程序,不仅仅局限于机器学习或数据科学项目。Flask为开发者提供了很高的灵活性,你可以自定义路由、模板、前端和后端的交互等等。对于初学者,Flask可能需要更长时间来学习,尤其是需要结合其他前端技术或数据库技术时。

不过,对于机器学习项目来说,我们还有其他部署方案。比如 Streamlit 和 Gradio,就为机器学习和数据科学应用提供了快速、专门化的解决方案。如果你的项目目标是快速展示和验证模型效果,那么 Streamlit 和 Gradio 是优秀的选择。这些框架提供了简单易用的 API 和丰富的可视化组件,让你可以用少量代码快速构建交互式应用程序,提高你的开发效率,也可以更好地展示工作成果。

1. 通过 Streamlit 部署聊天机器人

Streamlit是一个挺有名的专门为数据科学家和机器学习工程师设计的开源Python库,它可以迅速地将Python脚本转化为交互式Web应用。

Streamlit 的一些主要特点和亮点包括:

  • 简易性:Streamlit 的真正魅力在于它的简单性,只需几行代码,你就可以为其数据或模型创建交互式应用。
  • 无需前端经验:与传统的Web开发框架相比,使用 Streamlit,你不需要深入了解HTML、CSS或JavaScript,所有交互都是通过Python代码来管理的。
  • 实时交互:当你更改代码或数据时,Streamlit 应用会实时更新,这为迭代和实验提供了极大的便利。
  • 内置组件:Streamlit 附带了许多内置的可视化和交互组件,如滑块、按钮、表格等,可以无缝集成到你的应用中。
  • 数据集可视化:除了基本的图形和图表,Streamlit 还支持其他数据可视化库,如 Plotly、Matplotlib 和 Altair,使你能够轻松地展示数据。
  • 设计简洁:Streamlit 的界面设计简洁而优雅,使得应用程序看起来既专业又时尚。
  • 部署和共享:尽管 Streamlit 专注于创建应用,但它也有与部署和分享相关的工具和整合,如 Streamlit Sharing,允许你免费托管其应用。
  • 社区与生态系统:Streamlit 拥有一个积极的开源社区,定期提供新的功能更新、组件和扩展。

接下来就使用Streamlit部署聊天客服项目。

首先,安装Streamlit。

pip install streamlit

然后,简单的几行代码,就可以做出一个网页版的小程序。

import streamlit as st

# 设置标题
st.title('平方计算器')

# 创建一个滑块
number = st.slider("Select a number:", min_value=0, max_value=100)

# 显示选中数字的平方
st.write(f"Square of {number} is {number ** 2}")

streamlit run <your_script_name>.py(注意,必须是streamlit run命令,而不是通过python命令来跑程序)来运行程序,就可以在浏览器中看到它。

streamlit run 01_SimpleStreamlit.py

img

此时,在 localhost 的 8501 端口,程序开始启动。

img

下面就通过 Streamlit 来重构聊天机器人。

# 导入所需的库
import os
import streamlit as st
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Qdrant
from langchain.memory import ConversationSummaryMemory
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain
from langchain.document_loaders import PyPDFLoader
from langchain.document_loaders import Docx2txtLoader
from langchain.document_loaders import TextLoader

# 设置OpenAI API密钥
os.environ["OPENAI_API_KEY"] = 'Your OpenAI Key'  

# ChatBot类的实现
class ChatbotWithRetrieval:

    def __init__(self, dir):

        # 加载Documents
        base_dir = dir # 文档的存放目录
        documents = []
        for file in os.listdir(base_dir): 
            file_path = os.path.join(base_dir, file)
            if file.endswith('.pdf'):
                loader = PyPDFLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith('.docx') or file.endswith('.doc'):
                loader = Docx2txtLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith('.txt'):
                loader = TextLoader(file_path)
                documents.extend(loader.load())
        
        # 文本的分割
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=0)
        all_splits = text_splitter.split_documents(documents)
        
        # 向量数据库
        self.vectorstore = Qdrant.from_documents(
            documents=all_splits, # 以分块的文档
            embedding=OpenAIEmbeddings(), # 用OpenAI的Embedding Model做嵌入
            location=":memory:",  # in-memory 存储
            collection_name="my_documents",) # 指定collection_name
        
        # 初始化LLM
        self.llm = ChatOpenAI()
        
        # 初始化Memory
        self.memory = ConversationSummaryMemory(
            llm=self.llm, 
            memory_key="chat_history", 
            return_messages=True
            )
        
        # 设置Retrieval Chain
        retriever = self.vectorstore.as_retriever()
        self.qa = ConversationalRetrievalChain.from_llm(
            self.llm, 
            retriever=retriever, 
            memory=self.memory
            )

    def chat_loop(self):
        print("Chatbot 已启动! 输入'exit'来退出程序。")
        while True:
            user_input = input("你: ")
            if user_input.lower() == 'exit':
                print("再见!")
                break
            # 调用 Retrieval Chain  
            response = self.qa(user_input)
            print(f"Chatbot: {response['answer']}")

# Streamlit界面的创建
def main():
    st.title("易速鲜花聊天客服")

    # Check if the 'bot' attribute exists in the session state
    if "bot" not in st.session_state:
        st.session_state.bot = ChatbotWithRetrieval("OneFlower")

    user_input = st.text_input("请输入你的问题:")
    
    if user_input:
        response = st.session_state.bot.qa(user_input)
        st.write(f"Chatbot: {response['answer']}")

if __name__ == "__main__":
    main()

以下是使用 Streamlit 进行的更改和添加功能的简要说明。

  1. 界面创建:

    a. st.title("易速鲜花聊天客服"):设置 Web 应用程序的标题为“易速鲜花聊天客服”。

  2. 会话状态:

    a. 使用 st.session_state 来存储用户会话状态。这是 Streamlit 的一个特性,允许你在用户与应用程序交互时保存变量。

    b. if "bot" not in st.session_state:检查是否已经有一个 bot 实例存在于 session state 中。如果没有,就创建一个新的 ChatbotWithRetrieval 实例,并将其保存到 session state。这样做的好处是可以避免在每次用户与应用程序交互时重新初始化机器人。

  3. 用户交互:

    a. user_input = st.text_input("请输入你的问题:"):创建一个文本输入框供用户输入问题。当用户输入内容并提交后,代码会获取用户的输入,并使用聊天机器人的 qa 方法来获取响应。

    b. st.write(f"Chatbot: {response['answer']}"):在应用程序界面上显示机器人的响应。

  4. 主函数中,当脚本被执行时,它将启动 Streamlit 服务器,并显示创建的 Web 应用程序。

streamlit run 运行程序,就可以开始聊天了!

img

2. 通过 Gradio 部署聊天机器人

与 Streamlit 不同,Gradio 界面更侧重于模型的交互,据说上手也更简单。这使得 Gradio 非常适合展示和测试机器学习模型。我在GitHub上看到很多新的开源LLM都是提供一个Gradio UI界面来进行测试的。相比之下, Streamlit 则提供了更丰富的 Web 应用开发功能。

下面,我们先安装这个包。

pip install gradio

通过 Gradio 框架重构聊天机器人的程序代码如下:

# 导入所需的库
import os
import gradio as gr
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Qdrant
from langchain.memory import ConversationSummaryMemory
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain
from langchain.document_loaders import PyPDFLoader
from langchain.document_loaders import Docx2txtLoader
from langchain.document_loaders import TextLoader

# 设置OpenAI API密钥
os.environ["OPENAI_API_KEY"] = 'Your OpenAI Key'  

class ChatbotWithRetrieval:
    def __init__(self, dir):

        # 加载Documents
        base_dir = dir # 文档的存放目录
        documents = []
        for file in os.listdir(base_dir): 
            file_path = os.path.join(base_dir, file)
            if file.endswith('.pdf'):
                loader = PyPDFLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith('.docx') or file.endswith('.doc'):
                loader = Docx2txtLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith('.txt'):
                loader = TextLoader(file_path)
                documents.extend(loader.load())
        
        # 文本的分割
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=0)
        all_splits = text_splitter.split_documents(documents)
        
        # 向量数据库
        self.vectorstore = Qdrant.from_documents(
            documents=all_splits, # 以分块的文档
            embedding=OpenAIEmbeddings(), # 用OpenAI的Embedding Model做嵌入
            location=":memory:",  # in-memory 存储
            collection_name="my_documents",) # 指定collection_name
        
        # 初始化LLM
        self.llm = ChatOpenAI()
        
        # 初始化Memory
        self.memory = ConversationSummaryMemory(
            llm=self.llm, 
            memory_key="chat_history", 
            return_messages=True
            )
        # 初始化对话历史
        self.conversation_history = ""

        # 设置Retrieval Chain
        retriever = self.vectorstore.as_retriever()
        self.qa = ConversationalRetrievalChain.from_llm(
            self.llm, 
            retriever=retriever, 
            memory=self.memory
            )

    def get_response(self, user_input):  # 这是为 Gradio 创建的新函数
        response = self.qa(user_input)
        # 更新对话历史
        self.conversation_history += f"你: {user_input}\nChatbot: {response['answer']}\n"
        return self.conversation_history

if __name__ == "__main__":
    folder = "OneFlower"
    bot = ChatbotWithRetrieval(folder)

    # 定义 Gradio 界面
    interface = gr.Interface(
        fn=bot.get_response,  # 使用我们刚刚创建的函数
        inputs="text",  # 输入是文本
        outputs="text",  # 输出也是文本
        live=False,  # 实时更新,这样用户可以连续与模型交互
        title="易速鲜花智能客服",  # 界面标题
        description="请输入问题,然后点击提交。"  # 描述
    )
    interface.launch()  # 启动 Gradio 界面

以下是 Gradio 部分代码的详细解释。

  1. get_response(self, user_input):这个新函数是为 Gradio 创建的,它接收用户输入作为参数,并返回机器人的响应。为了保持聊天历史连续性,此函数将每次的用户输入和机器人的响应添加到 conversation_history,并返回整个聊天历史。
  2. 使用 gr.Interface() 来定义 Gradio 界面。fn=bot.get_response:设置界面的主函数为刚刚创建的 get_response 函数。live=False:确保实时更新是关闭的,这意味着用户需要点击提交按钮来发送他们的问题,而不是一边打字,一边生成回答的流模式(比较适合展示生成式模型)。
  3. 启动 Gradio 界面。interface.launch():调用这个方法会启动 Gradio 的 Web 服务器,并在默认的 Web 浏览器中打开一个新窗口,显示刚刚定义的界面,用户可以通过这个界面与机器人交互。

运行程序,聊天机器人在本地端口 7860 上启动。

img

img

这里,输入输出窗口的配置更加清晰,而且相对于原来的只记录一轮对话的机器人,这里我们增加了历史对话信息记录功能。

3. 总结

Streamlit 和 Gradio 都是让数据科学家和开发者能够快速为机器学习模型创建 Web UI 的框架。

  • Streamlit 是为数据应用、仪表板和可视化设计的。它提供了多种小部件,使得用户可以与数据和模型进行交互。它非常 Pythonic,意味着它的使用方式非常自然,对于熟悉Python的人来说非常直观。
  • Gradio 更多是为了展示和演示机器学习模型。它提供了一种快速的方法,使非技术用户也能与机器学习模型进行交互,无需编写复杂的代码。

以下是对它们特点进行的对比总结。

img