本地部署 Langchain-Chatchat & ChatGLM
1. 环境配置
- 首先,确保你的机器安装了 Python 3.8 - 3.11 (我们强烈推荐使用 Python3.11)。
$ python --version
Python 3.11.7
接着,创建一个虚拟环境,并在虚拟环境内安装项目的依赖
# 拉取仓库
$ git clone https://github.com/chatchat-space/Langchain-Chatchat.git
# 进入目录
$ cd Langchain-Chatchat
# 安装全部依赖
$ pip install -r requirements.txt
$ pip install -r requirements_api.txt
$ pip install -r requirements_webui.txt
# 默认依赖包括基本运行环境(FAISS向量库)。如果要使用 milvus/pg_vector 等向量库,请将 requirements.txt 中相应依赖取消注释再安装。
请注意,LangChain-Chatchat 0.2.x
系列是针对 Langchain 0.0.x
系列版本的,如果你使用的是 Langchain 0.1.x
系列版本,需要降级您的Langchain
版本。
2. 模型下载
如需在本地或离线环境下运行本项目,需要首先将项目所需的模型下载至本地,通常开源 LLM 与 Embedding 模型可以从 HuggingFace 下载。
以本项目中默认使用的 LLM 模型 THUDM/ChatGLM3-6B 与 Embedding 模型 BAAI/bge-large-zh 为例:
下载模型需要先安装 Git LFS ,然后运行
$ git lfs install
$ git clone https://huggingface.co/THUDM/chatglm3-6b
$ git clone https://huggingface.co/BAAI/bge-large-zh
3. 初始化知识库和配置文件
按照下列方式初始化自己的知识库和简单的复制配置文件
$ python copy_config_example.py
$ python init_database.py --recreate-vs
4. 一键启动
按照以下命令启动项目
$ python startup.py -a
5. 安装报错处理
1.ModuleNotFoundError: No module named 'pwd'
关于 ModuleNotFoundError: No module named 'pwd' ,可如下解决(我刚刚用这个方法解决了)
首先,创建 pwd.py 文件,内容如下
from os import *
from pwd import *
def get_username():
return getpwuid(getuid())[0]
然后,将 pwd.py 文件拷贝到 Python安装路径/Lib/ 下
最后,重新运行 python init_database.py --recreate-vs
2.huggingface网站无法打开
huggingface-cli
是 Hugging Face 官方提供的命令行工具,自带完善的下载功能。
2.1. 安装依赖
pip install -U huggingface_hub
2.2 设置环境变量
Linux
export HF_ENDPOINT=https://hf-mirror.com
Windows Powershell
$env:HF_ENDPOINT = "https://hf-mirror.com"
建议将上面这一行写入 ~/.bashrc
。
2.3 下载模型
huggingface-cli download --resume-download gpt2 --local-dir gpt2
2.4 下载数据集
huggingface-cli download --repo-type dataset --resume-download wikitext --local-dir wikitext
可以添加 --local-dir-use-symlinks False
参数禁用文件软链接,这样下载路径下所见即所得,详细解释请见上面提到的教程。
pip install torch==2.1.2+cpu torchvision==0.16.2+cpu torchaudio==2.1.2 -f https://download.pytorch.org/whl/torch_stable.html
EMBEDDING_DEVICE = "cpu"
LLM_DEVICE = "cpu"
"chatglm3-6b": {
"device": "cpu",
}
6. PDF解析优化
1.在Langchain-Chatchat\document_loaders目录下新建一个myminerpdfloader.py文件用于解析PDF。
import re from bs4 import BeautifulSoup from typing import List from langchain.document_loaders import PDFMinerPDFasHTMLLoader from langchain.docstore.document import Document class MinerPDFLoader(PDFMinerPDFasHTMLLoader): def load(self) -> List[Document]: loader = PDFMinerPDFasHTMLLoader(self.file_path) data = loader.load()[0] # print(data) soup = BeautifulSoup(data.page_content,'html.parser') content = soup.find_all('div') # print(content) cur_fs = None cur_text = '' snippets = [] # first collect all snippets that have the same font size for c in content: sp = c.find('span') if not sp: continue st = sp.get('style') if not st: continue fs = re.findall('font-size:(\d+)px',st) if not fs: continue fs = int(fs[0]) if not cur_fs: cur_fs = fs # removing page number pn = re.findall("^(\d+) \n$", c.text) if pn: continue # removing duplicate snippets (as headers/footers in a PDF appear on multiple pages so if we find duplicates it's safe to assume that it is redundant info) # if (cur_text,cur_fs) in snippets: # continue if fs == cur_fs: cur_text += c.text else: snippets.append((cur_text,cur_fs)) cur_fs = fs cur_text = c.text snippets.append((cur_text,cur_fs))
cur_idx = -1 semantic_snippets = [] # Assumption: headings have higher font size than their respective content for s in snippets: # if current snippet's font size > previous section's heading => it is a new heading if not semantic_snippets or s[1] > semantic_snippets[cur_idx].metadata['heading_font']: metadata={'heading':s[0], 'content_font': 0, 'title_font': 0, 'heading_font': s[1]} metadata.update(data.metadata) semantic_snippets.append(Document(page_content='',metadata=metadata)) cur_idx += 1 continue # if current snippet's font size <= previous section's content => content belongs to the same section (one can also create # a tree like structure for sub sections if needed but that may require some more thinking and may be data specific) if not semantic_snippets[cur_idx].metadata['title_font']: semantic_snippets[cur_idx].page_content += s[0] semantic_snippets[cur_idx].metadata['title_font'] = s[1] # print(semantic_snippets[cur_idx].metadata['title_font']) # cur_idx += 1 continue if s[1] < semantic_snippets[cur_idx].metadata['title_font']: semantic_snippets[cur_idx].page_content += s[0] semantic_snippets[cur_idx].metadata['content_font'] = max(s[1], semantic_snippets[cur_idx].metadata['content_font']) # print(semantic_snippets[cur_idx].metadata['title_font']) continue if s[1] == semantic_snippets[cur_idx].metadata['title_font']: metadata={'heading':semantic_snippets[cur_idx].metadata['heading'], 'content_font': 0, 'title_font': semantic_snippets[cur_idx].metadata['title_font'], 'heading_font': semantic_snippets[cur_idx].metadata['heading_font']} metadata.update(data.metadata) semantic_snippets.append(Document(page_content=s[0],metadata=metadata)) cur_idx += 1 continue #二层标题 #首先选取heading跟title #如果heading为空,则当前行为heading,转下一行 #如果title为空,则当前行为title,转下一行 #如果当前字体 #小于title 合并 保持heading 和title大小不变 #等于title换行 保持heading 和title大小不变 #大于tile换行 重置heading为当前行 # if current snippet's font size > previous section's content but less tha previous section's heading than also make a new # section (e.g. title of a pdf will have the highest font size but we don't want it to subsume all sections) metadata={'heading':s[0], 'content_font': 0, 'title_font': 0, 'heading_font': s[1]} metadata.update(data.metadata) semantic_snippets.append(Document(page_content=s[0], metadata=metadata)) cur_idx += 1 return semantic_snippets if __name__ == "__main__": loader = MinerPDFLoader(file_path="/Users/tonysong/Desktop/test.pdf") docs = loader.load() print(docs)
2.修改document_loaders\__init__.py文件,添加import MinerPDFLoader
from .mypdfloader import RapidOCRPDFLoader from .myimgloader import RapidOCRLoader from .mydocloader import RapidOCRDocLoader from .mypptloader import RapidOCRPPTLoader from .myminerpdfloader import MinerPDFLoader
3.修改server\knowledge_base\utils.py文件,修改PDF文件的加载类为MinerPDFLoader,并将类MinerPDFLoader添加import列表
# 修改PDF文件的加载类为MinerPDFLoader LOADER_DICT = { # "RapidOCRPDFLoader": [".pdf"], "MinerPDFLoader": [".pdf"], }
#将类MinerPDFLoader添加import列表
try:
if loader_name in ["RapidOCRPDFLoader", "RapidOCRLoader", "FilteredCSVLoader",
"RapidOCRDocLoader", "RapidOCRPPTLoader", "MinerPDFLoader"]:
document_loaders_module = importlib.import_module('document_loaders')
else:
document_loaders_module = importlib.import_module('langchain.document_loaders')
DocumentLoader = getattr(document_loaders_module, loader_name)