(25-4-01)基于本地知识库的自动问答系统(LangChain+ChatGLM+ModelScope/Huggingface部署): 构建和部署对话系统(1)

13.3.4  构建和部署对话系统

文件jina_serving.py定义了一个名为 KnowledgeBasedChatLLM 的类,用于初始化模型配置、加载文件、检索问题答案等操作。其中,LangChain 是文件jina_serving.py中的一个重要组件,它通过将自然语言处理技术与信息检索技术相结合,实现了以下功能:

  1. 模型管理与加载:通过 init_model 和 reinit_model 函数,实现了模型的初始化和重新加载,允许用户根据需求灵活选择大型语言模型和嵌入模型。
  2. 知识向量存储:利用 vector_store 函数初始化知识向量存储,允许将文档数据转化为向量表示,并存储在指定路径下,为后续的信息检索提供支持。
  3. 基于知识的答案生成:通过 predict 函数,实现了基于知识的聊天功能。LangChain 在其中发挥关键作用,通过结合大型语言模型、嵌入模型和向量存储,从海量数据中提取知识,并根据用户查询生成相关的答案。LangChain 通过信息检索和答案生成的流程,为聊天系统提供了强大的智能问答能力,能够根据用户的提问,快速、准确地回复相关的知识内容。

文件jina_serving.py具体实现流程如下所示。

(1)导入需要的库,特别是跟LangChain相关的库。

import datetime
import os
from typing import List

import nltk
import qdrant_client
import sentence_transformers
import torch
from duckduckgo_search import ddg
from duckduckgo_search.utils import SESSION
from langchain.chains import RetrievalQA
from langchain.document_loaders import UnstructuredFileLoader
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain.prompts import PromptTemplate
from langchain.prompts.prompt import PromptTemplate
from langchain.vectorstores import Qdrant
from lcserve import serving

from chatllm import ChatLLM
from chinese_text_splitter import ChineseTextSplitter
from config import *

(2)编写下面的代码,将如下所示的配置信息传递给其他模块或函数使用:

  1. nltk.data.path = [os.path.join(os.path.dirname(__file__), "nltk_data")] + nltk.data.path:设置了 NLTK(Natural Language Toolkit)的数据路径。NLTK 是一个 Python 库,用于自然语言处理任务,例如标记化、词性标注、句法分析等。这行代码将 NLTK 数据路径设置为当前文件所在目录下的 "nltk_data" 文件夹,以确保 NLTK 能够找到所需的数据文件。
  2. embedding_model_dict, llm_model_dict, EMBEDDING_DEVICE, LLM_DEVICE, VECTOR_STORE_PATH, COLLECTION_NAME, num_gpus, init_llm, init_embedding_model:这些变量包含了一些配置信息,如嵌入模型字典、LLM(Large Language Model)模型字典、嵌入设备、LLM 设备、向量存储路径、集合名称、GPU 数量以及初始化的 LLM 和嵌入模型。这些信息可能被其他模块或函数用于初始化模型、加载数据、配置计算设备等任务。
nltk.data.path = [os.path.join(os.path.dirname(__file__), "nltk_data")
                  ] + nltk.data.path

embedding_model_dict = embedding_model_dict
llm_model_dict = llm_model_dict
EMBEDDING_DEVICE = EMBEDDING_DEVICE
LLM_DEVICE = LLM_DEVICE
VECTOR_STORE_PATH = VECTOR_STORE_PATH
COLLECTION_NAME = COLLECTION_NAME
num_gpus = num_gpus
init_llm = init_llm
init_embedding_model = init_embedding_model

(3)函数search_web用于在网络上进行查询并返回查询结果的网页内容。该函数首先设置代理,然后使用指定的查询字符串进行搜索,并将搜索结果的网页内容合并到一个字符串中,最后将合并后的网页内容作为函数的返回值。

def search_web(query):

    SESSION.proxies = {
        "http": f"socks5h://localhost:7890",
        "https": f"socks5h://localhost:7890"
    }
    results = ddg(query)
    web_content = ''
    if results:
        for result in results:
            web_content += result['body']
    return web_content

(4)定义类KnowledgeBasedChatLLM,用于初始化模型的配置信息。通过调用 init_model_config方法,可以配置和初始化大型语言模型(LLM)和嵌入模型。方法init_model_config接受两个参数、large_language_model和 embedding_model,分别指定要使用的大型语言模型和嵌入模型的名称。在初始化模型配置信息的过程中,根据指定的大型语言模型名称,确定所需的模型类型(例如 'chatglm'、'belle'、'vicuna'),并从预先定义的模型字典中获取相应的模型路径。同时,根据指定的嵌入模型名称,从预定义的嵌入模型字典中获取相应的模型名称,并使用HuggingFaceEmbeddings和 SentenceTransformer初始化嵌入模型。最后,调用load_llm方法加载大型语言模型,并指定设备和 GPU 数量。

class KnowledgeBasedChatLLM:

    llm: object = None
    embeddings: object = None

    def init_model_config(
        self,
        large_language_model: str = init_llm,
        embedding_model: str = init_embedding_model,
    ):
        self.llm = ChatLLM()
        if 'chatglm' in large_language_model.lower():
            self.llm.model_type = 'chatglm'
            self.llm.model_name_or_path = llm_model_dict['chatglm'][
                large_language_model]
        elif 'belle' in large_language_model.lower():
            self.llm.model_type = 'belle'
            self.llm.model_name_or_path = llm_model_dict['belle'][
                large_language_model]
        elif 'vicuna' in large_language_model.lower():
            self.llm.model_type = 'vicuna'
            self.llm.model_name_or_path = llm_model_dict['vicuna'][
                large_language_model]
        self.embeddings = HuggingFaceEmbeddings(
            model_name=embedding_model_dict[embedding_model], )
        self.embeddings.client = sentence_transformers.SentenceTransformer(
            self.embeddings.model_name, device=EMBEDDING_DEVICE)
        self.llm.load_llm(llm_device=LLM_DEVICE, num_gpus=num_gpus)

(5)定义方法init_knowledge_vector_store,用于初始化知识向量存储,它接受一个文件路径或文件路径列表作为输入参数,将指定路径下的文件加载到向量存储中。

  1. 首先,它检查输入的路径是否存在,如果不存在,则返回提示信息 "路径不存在"。
  2. 如果输入的路径是文件,则调用 load_file 方法加载文件,并将加载成功的文件路径添加到 loaded_files 列表中;如果输入的路径是目录,则遍历目录下的所有文件,依次加载每个文件,并将加载成功的文件路径添加到 loaded_files 列表中。如果输入的路径是文件列表,则依次加载列表中的每个文件,并将加载成功的文件路径添加到 loaded_files 列表中;
  3. 如果成功加载了文件,则根据是否存在向量存储路径和路径是否为目录,初始化一个 Qdrant 实例,并将文档加入向量存储中。
  4. 最后,返回加载结果信息和成功加载的文件列表。如果没有成功加载文件,则返回相应的提示信息。

总之,方法init_knowledge_vector_store用于加载指定路径下的文件,并将它们存储为向量,以便后续的知识检索任务使用。

    def init_knowledge_vector_store(self,
                                    filepath: str or List[str],):
        loaded_files = []
        if isinstance(filepath, str):
            if not os.path.exists(filepath):
                return "路径不存在"
            elif os.path.isfile(filepath):
                file = os.path.split(filepath)[-1]
                try:
                    docs = self.load_file(filepath)
                    print(f"{file} 已成功加载")
                    loaded_files.append(filepath)
                except Exception as e:
                    print(e)
                    print(f"{file} 未能成功加载")
                    return f"{file} 未能成功加载"
            elif os.path.isdir(filepath):
                docs = []
                for file in os.listdir(filepath):
                    fullfilepath = os.path.join(filepath, file)
                    try:
                        docs += self.load_file(fullfilepath)
                        print(f"{file} 已成功加载")
                        loaded_files.append(fullfilepath)
                    except Exception as e:
                        print(e)
                        print(f"{file} 未能成功加载")
        else:
            docs = []
            for file in filepath:
                try:
                    docs += self.load_file(file)
                    print(f"{file} 已成功加载")
                    loaded_files.append(file)
                except Exception as e:
                    print(e)
                    print(f"{file} 未能成功加载")
        if len(docs) > 0:
            if VECTOR_STORE_PATH and os.path.isdir(VECTOR_STORE_PATH):
                vector_store = Qdrant.from_documents(
                    docs,
                    self.embeddings,
                    path=VECTOR_STORE_PATH,
                    collection_name=COLLECTION_NAME,
                )
                vector_store.add_documents(docs)
            else:
                vector_store = Qdrant.from_documents(
                    docs,
                    self.embeddings,
                    path=VECTOR_STORE_PATH,
                    collection_name=COLLECTION_NAME,
                )
            return "文件均未成功加载,请检查依赖包或文件路径。", loaded_files
        else:
            print("文件均未成功加载,请检查依赖包或文件路径。")
            return "文件均未成功加载,请检查依赖包或文件路径。", loaded_files

(6)方法get_knowledge_based_answer用于基于已知信息回答用户的问题,该方法用于根据已知的网络内容和历史记录,以及用户提供的问题,生成相应的答案结果。方法get_knowledge_based_answer接受以下参数:查询字符串 query、网络内容 web_content、返回结果的数量 top_k、历史记录的长度 history_len、温度 temperature、top-p 抽样的阈值 top_p,以及历史记录 history。

在执行方法get_knowledge_based_answer的过程中,首先根据输入的网络内容,构建一个提示模板,用于引导答案生成。然后根据历史记录的长度,将历史记录进行截取。接着,它初始化一个 Qdrant 客户端,以及一个基于 LLN 和 Qdrant 的知识链,用于检索问题的答案。最后,它返回根据输入的查询字符串生成的答案结果。

    def get_knowledge_based_answer(self,
                                   query,
                                   web_content,
                                   top_k: int = 6,
                                   history_len: int = 3,
                                   temperature: float = 0.01,
                                   top_p: float = 0.1,
                                   history=[]):
        self.llm.temperature = temperature
        self.llm.top_p = top_p
        self.history_len = history_len
        self.top_k = top_k
        if web_content:
            prompt_template = f"""基于以下已知信息,简洁和专业的来回答用户的问题。
                                如果无法从中得到答案,请说 "根据已知信息无法回答该问题" 或 "没有提供足够的相关信息",不允许在答案中添加编造成分,答案请使用中文。
                                已知网络检索内容:{web_content}""" + """
                                已知内容:
                                {context}
                                问题:
                                {question}"""
        else:
            prompt_template = """基于以下已知信息,请简洁并专业地回答用户的问题。
                如果无法从中得到答案,请说 "根据已知信息无法回答该问题" 或 "没有提供足够的相关信息"。不允许在答案中添加编造成分。另外,答案请使用中文。

                已知内容:
                {context}

                问题:
                {question}"""
        prompt = PromptTemplate(template=prompt_template,
                                input_variables=["context", "question"])
        self.llm.history = history[
            -self.history_len:] if self.history_len > 0 else []
        client = qdrant_client.QdrantClient(path=VECTOR_STORE_PATH,
                                            prefer_grpc=True)
        qdrant = Qdrant(client=client,
                        collection_name=COLLECTION_NAME,
                        embedding_function=self.embeddings.embed_query)
        knowledge_chain = RetrievalQA.from_llm(
            llm=self.llm,
            retriever=qdrant.as_retriever(search_kwargs={"k": self.top_k}),
            prompt=prompt)
        knowledge_chain.combine_documents_chain.document_prompt = PromptTemplate(
            input_variables=["page_content"], template="{page_content}")

        knowledge_chain.return_source_documents = True

        result = knowledge_chain({"query": query})
        return result

(7)方法load_file用于加载文件并返回文档列表,此方法、返回加载后的文档列表,接受一个文件路径作为输入参数 filepath:

  1. 如果文件路径以 ".md" 结尾,则将文件视为 Markdown 格式,使用 UnstructuredFileLoader 加载器以 "elements" 模式加载文件,并返回加载后的文档列表。
  2. 如果文件路径以 ".pdf" 结尾,则将文件视为 PDF 格式,使用 UnstructuredFileLoader 加载器加载文件,并使用 ChineseTextSplitter 对文本进行拆分。
  3. 如果文件路径不以 ".md" 或 ".pdf" 结尾,则将文件视为普通文本文件,同样使用 UnstructuredFileLoader 加载器以 "elements" 模式加载文件,并使用 ChineseTextSplitter 对文本进行拆分。
 def load_file(self, filepath):
        if filepath.lower().endswith(".md"):
            loader = UnstructuredFileLoader(filepath, mode="elements")
            docs = loader.load()
        elif filepath.lower().endswith(".pdf"):
            loader = UnstructuredFileLoader(filepath)
            textsplitter = ChineseTextSplitter(pdf=True)
            docs = loader.load_and_split(textsplitter)
        else:
            loader = UnstructuredFileLoader(filepath, mode="elements")
            textsplitter = ChineseTextSplitter(pdf=False)
            docs = loader.load_and_split(text_splitter=textsplitter)
        return docs

(8)定义函数 init_model,用于初始化知识基础的聊天系统。在函数内部,尝试通过调用类KnowledgeBasedChatLLM中的方法init_model_config来配置和初始化模型,然后调用 llm._call("你好") 方法进行模型检验。如果初始化过程顺利完成,函数返回字符串 "初始模型已成功加载",表示模型加载成功;否则,捕获异常并返回字符串 "模型未成功加载,请检查后重新尝试",表示模型加载失败,需要进行重新尝试。

knowladge_based_chat_llm = KnowledgeBasedChatLLM()

def init_model():
    try:
        knowladge_based_chat_llm.init_model_config()
        knowladge_based_chat_llm.llm._call("你好")
        return """初始模型已成功加载"""
    except Exception as e:

        return """模型未成功加载,请检查后重新尝试"""

(9)定义一个、@serving装饰的函数 reinit_model,用于重新初始化模型。该函数接受两个参数 large_language_model 和 embedding_model,分别指定要重新加载的大型语言模型和嵌入模型的名称。

@serving
def reinit_model(large_language_model: str, embedding_model: str):
    try:
        knowladge_based_chat_llm.init_model_config(
            large_language_model=large_language_model,
            embedding_model=embedding_model)
        model_status = """模型已成功重新加载"""
    except Exception as e:
        model_status = """模型未成功加载,请检查后重新尝试"""
    return model_status

在函数 reinit_model的内部,尝试调用 knowladge_based_chat_llm 对象的 init_model_config 方法,通过传入新的模型名称来重新配置和初始化模型。如果重新初始化过程顺利完成,函数返回字符串 "模型已成功重新加载";否则,捕获异常并返回字符串 "模型未成功加载,请检查后重新尝试",表示重新加载模型失败,需要进行重新尝试。

(10)定义@serving函数 vector_store,用于初始化知识向量存储。它接受一个文件路径或文件路径列表作为输入参数,并调用 KnowledgeBasedChatLLM 对象的 init_knowledge_vector_store 方法来初始化知识向量存储。这个函数适用于处理向量存储的请求,并提供了一种方便的方式来在服务端初始化知识向量存储。

@serving
def vector_store(file_path: str or List[str]):

    vector_store_state, loaded_files = knowladge_based_chat_llm.init_knowledge_vector_store(
        file_path)
    return vector_store_state

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Langchain和ChatGLM是一种本地知识库对话语言模型的组合。Langchain是一个用于构建本地知识库的工具,而ChatGLM则是基于Langchain的一个对话语言模型。它们通常一起部署在一起,并且具有较高的耦合性。通过Langchain和ChatGLM的结合,可以实现一个基于本地知识库问答系统Langchain-ChatGLM项目是一个开源项目,可以在GitHub上找到相关的代码和文档。ChatGLM-6B是一种开源的、支持中英双语的对话语言模型,基于General Language Model (GLM)架构,具有62亿参数。所以,Langchain和ChatGLM是一种用于构建本地知识库并进行问答的工具和模型的组合。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [LangChain + ChatGLM 实现本地知识库问答](https://blog.csdn.net/bruce__ray/article/details/131179563)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [chatglm+langchain](https://blog.csdn.net/qq_24729325/article/details/131515519)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值