基于ChatGLM+Langchain离线搭建本地知识库(免费)

目录

简介

服务部署

实现本地知识库

测试

番外


简介

ChatGLM-6B是清华大学发布的一个开源的中英双语对话机器人。基于 General Language Model (GLM) 架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级的显卡上进行本地部署(INT4 量化级别下最低只需 6GB 显存)。

LangChain提供了丰富的生态,可以非常方便的封装自己的工具,并接入到LangcChain的生态中,从而实现语言模型的交互,将多个组件链接在一起,并集成额外的资源,例如 API 和数据库。

服务部署

我是在一台离线服务器上,GPU内存16G,其中python3.10以上,torch1.10以上。首先github下载ChatGLM-6B(https://github.com/THUDM/ChatGLM-6B),方便调用接口,里面有一个 requirements.txt文件,直接安装里面环境即可,然后在Huggingface下载模型chatglm-6b(https://huggingface.co/THUDM/chatglm3-6b),最后将下载好的模型离线打包到离线服务器上。如下所示,其中kownledge文件夹里面包含了我要输入的知识文档(自己的一些文档、pdf、csv文件等)。

当环境搭建好之后,进入ChatGLm-6B文件夹下,打开api.py文件,将tokenizer和model的模型路径修改成从Huggingface下载下来的chatglm-6b模型路径,这里我用的是相对路径。

然后在服务器上运行api.py文件,服务在端口8000运行。

写一个测试代码api_access.py,看看服务是否能被正常使用。值得注意的是,如果你是在本地运行,这里的url写localhost:8000或者127.0.0.1:8000,如果是服务器运行,则写服务器的ip地址。

import requests

def chat(prompt, history):
    resp = requests.post(
        #url = 'http://127.0.0.1:8000',
        url = 'http://172.27.171.194:8000',
        json = {"prompt": prompt, "history": history },
        headers = {"Content-Type": "application/json;charset=utf-8"}
    )
    return resp.json()['response'], resp.json()['history']


history = []
while True:
    response, history = chat(input("Question:"), history)
    print('Answer:',response)

运行结果如下所示,说明该api服务能够正常使用。

实现本地知识库

首先在Huggingface下载向量化模型,我选择了text2vec-base-chinese(https://huggingface.co/shibing624/text2vec-base-chinese/tree/main

然后编写一个embedding.py文件,主要存放各种方法,完整代码如下所示。值得注意的是,由于我的服务器有多张显卡,因此我将cuda设置为1,你如果只有一张显卡,就直接是cuda:0。

from langchain_community.document_loaders import  Docx2txtLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from transformers import BertModel, BertTokenizer
import torch
import os

# -*- coding: utf-8 -*-

class EmbeddingFunction:
    def __init__(self, embedding_function):
        self.embedding_function = embedding_function

    def embed_query(self, query):
        return self.embedding_function(query)

    def embed_documents(self, documents):
        return [self.embedding_function(doc) for doc in documents]


class EmbeddingRetriever:
    def __init__(self):
        # 加载embedding
        self.embedding_model_dict = {
            "text2vec3": "shibing624/text2vec-base-chinese",
            "bert-base-chinese": "/home/ai/bert-base-chinese",
        }

    def load_documents(self,directory='kownledge'):
        documents = []
        for item in os.listdir(directory):
            if item.endswith("docx") or item.endswith("pdf"):
                split_docs = self.add_document(directory, item)
                documents.extend(split_docs)
        return documents

    def add_document(self, directory='kownledge', doc_name=''):
        file_path = os.path.join(directory, doc_name)
        if doc_name.endswith("docx"):
            loader = Docx2txtLoader(file_path=file_path)
        elif doc_name.endswith("pdf"):
            loader = PyPDFLoader(file_path=file_path)
        data = loader.load()
        text_spliter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)

        split_docs = text_spliter.split_documents(data)
        return split_docs

    def load_embedding_mode(self,model_name="text2vec3"):
        model_path = self.embedding_model_dict[model_name]
        tokenizer = BertTokenizer.from_pretrained(model_path)
        model = BertModel.from_pretrained(model_path).to("cuda:1")

        def embedding_function(text):
            inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512).to("cuda:1")
            with torch.no_grad():
                outputs = model(**inputs)
            embedding = outputs.last_hidden_state.mean(dim=1).squeeze().cpu().numpy().tolist()
            return embedding

        return embedding_function
    def store_chroma(self,docs, db):
        db.add_documents(docs)
        db.persist()
        return db

简单解释如下,在load_documents和add_document方法中,由于我的知识文档是docx和pdf格式的,因此我就只写了两个类型,你如果有其他类型比如csv或者txt可以修改调用方式,如:

from langchain_community.document_loaders import  TextLoader,CSVLoader

测试

编写一个问答代码my_qa.py,完整代码如下所示,记得修改url地址。

from langchain_community.vectorstores import Chroma
import requests
from embedding import EmbeddingRetriever,EmbeddingFunction

# -*- coding: utf-8 -*-

def chat(prompt, history=None):
    plyload = {
        "prompt": prompt, "history": [] if not history else history
    }
    headers = {"Content-Type": "application/json"}
    resp = requests.post(
        url = 'http://172.27.171.194:8000',
        json = plyload,
        headers = headers
    ).json()
    return resp['response']

retriever = EmbeddingRetriever()
embedding_function = EmbeddingFunction(retriever.load_embedding_mode())
db = Chroma(embedding_function=embedding_function, persist_directory='VectorStore')

# 添加知识文档
if True:
    documents = retriever.add_document(doc_name='课程表.docx')
    #documents = retriever.load_documents()
    db = retriever.store_chroma(documents,db)

while True:
    query = input("question:")

    similar_docs = db.similarity_search(query,k=3)

    prompt = "基于以下给出的资料,回答问题:\n"
    for doc in similar_docs:
        prompt += f"{doc.page_content}\n"
    prompt += f"问题: {query}"

    response = chat(prompt,[])
    print("Bot:", response)

简单解释如下:

在服务器上运行python my_qa.py,结果如下:

可以看到,准确度还是相当不错的。如果自己输入的知识库数量越多,回答越准确。

最后看看我的服务器上的文档位置。

可能出现的问题

1、解决transformers和sentence-transformers版本冲突问题

使用pip install -U sentence-transformers下载sentence-transformers时会下载最新版2.7.0并且把最新版的transformers4.39.3一起附带下载下来,但是在ChatGLM中要求的transformers版本是4.27.1,因此如果使用最新版的transformers在运行api.py接口时会报错提示没有xxxx属性。解决方式有两种,第一种就是手动降低版本(但可能会报出其他错误),第二种就是修改chatglm-6b的配置文件,如下所示:

就是将从Huggingface下载的模型chatglm-6b下的tokenization_chatglm.py文件进行修改,将第222行的代码注释,放在第196行,也就是super().__init__上面。

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值