易速鲜花聊天客服机器人的开发 | 豆包MarsCode AI 刷题

216 阅读7分钟

本文是易速鲜花聊天客服机器人的开发课程的实践记录。因为原课程的使用的Embedding消耗豆包的token太大,所以在实践中,Embedding模型使用的是ISOISS/jina-embeddings-v3-tei,llm使用的是豆包的Doubao-pro-32k。然后,实践记录中使用的平台是Colab,因为课程AI练中学空间只有10GB。

注册豆包API账号

因为要使用到豆包的Doubao-pro-32k,所以我们需要先注册火山方舟的账户,然后获取API Key、Base Url等:

  1. 访问火山方舟官网完成账号注册和服务授权 image.png
  2. 在控制台的 API Key管理 页面中创建 API Key。image.png
  3. 在控制台的 在线推理页面 中创建推理接入点,推荐使用doubao-pro-32k作为主力模型,模型部署完成后即可获得模型的base_url和model_endpoint image.png

安装依赖和配置

运行下面代码安装所需要的依赖:

!pip install -U langchain-community
!pip install -U langchain_openai
!pip install -U volcengine-python-sdk[ark]
!pip install pypdf
!pip install docx2txt
!pip install qdrant-client
!pip install gradio

接着是配置环境变量,在Colab中先将环境变量填入Secret中:

截屏2024-11-16 18.26.20.png

接着运行下面代码设置环境变量:

from google.colab import userdata
import os
os.environ["LLM_MODELEND"] = userdata.get("LLM_MODELEND")
os.environ["OPENAI_API_KEY"] = userdata.get("OPENAI_API_KEY")
os.environ["OPENAI_BASE_URL"] = userdata.get("OPENAI_BASE_URL")

上传文件

将AI练中学给的三个文件上传到docs文件夹中:易速鲜花员工手册.pdf 易速鲜花运营指南.docx 花语大全.txt

截屏2024-11-16 19.14.25.png

实现embedding类和ChatBot类

在这里,我们主要要实现两个类:embedding类和ChatBot类,分别用于检索和对话。

嵌入模型

定义了一个 OpenSourceEmbeddings 类,继承自 langchainEmbeddings

该类使用预训练模型(如 ISOISS/jina-embeddings-v3-tei)生成文档和查询的嵌入向量。

作用:

  • embed_documents: 为多个文档生成向量表示,用于检索。
  • embed_query: 为单一查询生成向量表示。

ChatBot类的实现

实现一个类 ChatbotWithRetrieval,负责初始化和运行支持检索功能的聊天机器人。

(1) 文档加载
  • 支持加载 PDF、Word 和 TXT 文档。
  • 使用 langchain 的文档加载器读取内容并存入列表 documents
  • 如果没有有效文档,抛出异常。
(2) 文本分割
  • 使用 RecursiveCharacterTextSplitter 将文档拆分为 长度为200字符的文本块
  • 这样处理有利于后续的向量化和检索操作。
(3) 构建向量数据库
  • 使用 Qdrant 向量数据库,基于分割的文档和嵌入模型 open_source_model 生成存储。
  • 数据存储在内存中(location=":memory:")。
(4) 初始化 LLM 和记忆机制
  • 使用 OpenAI 的 ChatGPT 模型 ChatOpenAI,配置:

    • 温度:调整输出随机性。
    • 最大 Token 数量:限制单次响应长度。
  • ConversationSummaryMemory:实现会话记忆,用于保存聊天历史。

(5) 设置检索链
  • 将向量数据库封装成一个检索器。
  • 使用 ConversationalRetrievalChain 构建一个包含检索能力的聊天链。
(6) 自定义 Prompt
  • 设置机器人角色为“花卉行家”,并限制回答字数,确保输出简洁专业。

运行下面代码以示例化聊天机器人:

"""
本文件是【易速鲜花聊天客服机器人的开发(上)】章节的配套代码,课程链接:https://juejin.cn/book/7387702347436130304/section/7388069967780347967
"""
# 导入所需的库
import os  # 缺失的导入
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Qdrant
from langchain.memory import ConversationSummaryMemory
from langchain.chains import ConversationalRetrievalChain
from langchain.document_loaders import PyPDFLoader, Docx2txtLoader, TextLoader
from langchain.embeddings.base import Embeddings
from langchain_openai import ChatOpenAI  # ChatOpenAI模型
from transformers import AutoModel
from langchain.schema import HumanMessage, SystemMessage
from langchain.prompts import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)



class OpenSourceEmbeddings(Embeddings):
    def __init__(self, model_name: str):
        """
        初始化开源嵌入模型。
        :param model_name: 嵌入模型的名称,例如 'ISOISS/jina-embeddings-v3-tei'
        """
        self.model = AutoModel.from_pretrained(model_name, trust_remote_code=True)

    def embed_documents(self, texts: list[str]) -> list[list[float]]:
        """
        批量生成文档嵌入向量。
        """
        return self.model.encode(texts,  task="retrieval.passage").tolist()

    def embed_query(self, text: str) -> list[float]:
        """
        生成单个查询的嵌入向量。
        """
        return self.model.encode([text], task="retrieval.query")[0].tolist()

# 替换为 ISOISS/jina-embeddings-v3-tei 模型
open_source_model = OpenSourceEmbeddings(model_name="ISOISS/jina-embeddings-v3-tei")

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

    def __init__(self, dir_path):
        """
        初始化Chatbot类
        :param dir_path: 文档的存放目录
        """
        # 检查目录是否存在
        if not os.path.isdir(dir_path):
            raise ValueError(f"提供的路径无效: {dir_path}")

        # 加载文档
        documents = []
        for file in os.listdir(dir_path):
            file_path = os.path.join(dir_path, file)
            if file.endswith(".pdf"):
                loader = PyPDFLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith((".docx", ".doc")):
                loader = Docx2txtLoader(file_path)
                documents.extend(loader.load())
            elif file.endswith(".txt"):
                loader = TextLoader(file_path)
                documents.extend(loader.load())

        if not documents:
            raise ValueError("未能加载任何有效文档,请检查文件类型和路径。")
        #print(f"加载到的文档: {documents}")

        # 文本分割
        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=open_source_model,  # 使用自定义嵌入模型
            location=":memory:",  # 内存存储
            collection_name="my_documents",
        )

        print(f"向量数据库文档数量: {len(all_splits)}")

        # 初始化LLM
        self.llm = ChatOpenAI(
            model=os.environ["LLM_MODELEND"],
            temperature=0.9,
            max_tokens=512,
            openai_api_key=os.environ.get("OPENAI_API_KEY"),
            openai_api_base=os.environ.get("OPENAI_BASE_URL")
        )

        # 初始化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(
            llm=self.llm, retriever=retriever, memory=self.memory
        )

        # 初始化Prompt
        self.prompt = ChatPromptTemplate(
            messages=[
                SystemMessagePromptTemplate.from_template("你是一个花卉行家。你通常的回答不超过30字。"),
                MessagesPlaceholder(variable_name="chat_history"),
                HumanMessagePromptTemplate.from_template("{question}")
            ]
        )

    # 交互对话的函数
    def chat_loop(self):
        """
        启动与用户的交互会话。
        """
        print("Chatbot 已启动! 输入 'exit' 来退出程序。")
        while True:
            user_input = input("你: ")
            if user_input.lower() == "exit":
                print("再见!")
                break
            try:
                response = self.qa({"question": user_input})
                print(f"Chatbot: {response['answer']}")
            except Exception as e:
                print(f"发生错误: {e}")

    def ask(self, user_input):
      """处理用户输入并返回模型回答"""
      try:
          response = self.qa({"question": user_input})
          return response['answer']
      except Exception as e:
          return f"对话失败: {e}"

# 更新文档目录
folder = "/content/docs"

# 初始化机器人
bot = ChatbotWithRetrieval(folder)

交互

运行下面代码,我们就可以和LLM聊天交互:

if __name__ == "__main__":
    try:
        bot.chat_loop()
    except Exception as e:
        print(f"启动失败: {e}")

尝试输入“适合送给准备结婚的女朋友的花”、“员工应该遵守什么规定”等问题,我们可以得到llm根据本地文档检索得到的答案:

你: 适合送给准备结婚的女朋友的花

Chatbot: Violet(紫罗兰)比较适合,因为它代表着永恒的爱、纯洁和贞洁,这些寓意很适合送给即将结婚的女朋友。

你: 员工应该遵守什么规定

Chatbot: 员工应遵守以下规定: 1. 人事管理制度方面: - 人事部统一将用人部门招人计划呈报总经理签字审批后由人事部负责招聘。 - 公司员工分为管理公司员工和代商家招聘的员工。 - 招聘遵循公平、公正、择优录取原则。 - 应聘人员须提供本人如实填写的《入职申请表》一份、近期免冠标准照片四张、身份证复印件(验明正件)。 2. 工作纪律方面: - 工作时间内,不得擅离工作岗位,不得在工作场所嬉戏、打闹、做私活、看与本岗位无关的书报、登陆与工作无关的网页等一切与工作无关的行为,否则按公司相关规定处罚。 - 公司管理者应指导、团结、督促、激励下属,树立管理意识,提高工作能力及服务质量。 - 员工上班时间须着工作装,佩戴胸牌(胸牌佩戴在左胸口),衣着整洁,仪容仪表要符合公司基本要求,如有违反按公司相关规定执行。 3. 员工手册相关规定: - 员工离职时,须将手册交回人力资源部,否则将罚款50元。 - 胸牌如有遗失,应立即到人力资源部补办,并交纳赔偿及手续费(工本费),员工离职时必须将胸牌、手册等证件、资料交回有关部门,否则将做出相应的经济赔偿。 - 本《员工手册》解释权属贵州易速鲜花旅游文化股份有限公司人力资源部;公司有权根据工作需要对本《员工手册》有关内容修订,修订时公司将以适当方式向员工告知;本《员工手册》若有与有关国家法律法规不一致的,按国家有关法律法规执行。

你:

截屏2024-11-16 19.45.28.png