广告行业中那些趣事系列87:基于LangChain-Chatchat构建本地知识库问答应用

01 LangChain-Chatchat项目介绍

Langchain-Chatchat 是一个基于 ChatGLM 大语言模型与 Langchain 应用框架实现,开源、可离线部署的检索增强生成 (RAG) 大模型的本地知识库问答应用项目。目前(截止20241113)LangChain-Chatchat源码的github项目已经有5.6K的fork和32K的star了,可以说非常流行。github地址如下:

https://github.com/chatchat-space/Langchain-Chatchat

02 LangChain-Chatchat项目实现原理

本项目实现原理如下图所示,主要包括三块,第一块是构建本地知识库的向量检索库。为了提升LLM在专业领域的问答效果,将本地知识库中和问题相关的知识作为上下文添加到问题中提供给LLM,丰富的上下文可以提供更多的相关知识,让LLM生成效果更好。主要过程包括加载文件 -> 读取文本 -> 文本分割 -> 文本向量化 ;第二块就是将问题去本地知识的向量检索库中去查找最相关的tok个知识。主要包括问句向量化 -> 在文本向量中匹配出与问句向量最相似的 top k 个;第三块就是将匹配的相关知识作为上下文和问题一起添加到prompt模本中提交给LLM来生成答案。

图片

从文档处理角度看实现流程如下图所示:

图片

下面是一个实际的例子说明,比如我们基于书、教材、行业规范、操作手册等构建了一个知识库,然后用户输入"太平天国运动的历史影响",就会去知识库中找到和该问题相关的知识。将找到的相关知识作为上下文和问题一起添加到提示词模版中,调用像GPT-4、文心一言等大语言模型来生成答案。示例说明如下图所示:

图片

03 LangChain-Chatchat项目部署

如果是想供个人学习使用而非业务需求,可以考虑在autoDL上租GPU配置,亲测3分钟内启动使用。因为有已经配好的镜像可以直接使用,所以流程非常简单,部署链接如下:

https://blog.youkuaiyun.com/wuexp/article/details/133928455

3.1 软硬件部署要求

软件要求如下图所示:

图片

硬件要求如下图所示:

图片

3.2 详细部署流程

3.2.1 下载项目文件和模型

LangChain-Chatchat运行至少需要两个模型(默认是用于对话的chatglm3-6b和用于embedding的bge-large-zh)。模型等大文件可以在HuggingFace上下载,建议手动下载大文件后再上传,速度更快。

具体步骤:

# 方法一:开启 git lfs 后直接 git clone 仓库
git lfs install
git clone https://huggingface.co/baichuan-inc/Baichuan2-13B-Chat


# 方法二:下载仓库基本信息,不下载大文件,然后再通过手动下载大文件
# 在model/下开一个jupyter notebook,通过如下代码连接huggingface
import os
os.environ['http_proxy'] = 'http://nbproxy.mlp.oppo.local:8888'
os.environ['https_proxy'] = 'http://nbproxy.mlp.oppo.local:8888'


# clone小文件
!GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/BAAI/bge-reranker-large


# 手动下载大文件后上传

方法一的方式会将仓库中的 git 记录一并下载,导致下载下来的文件比较大,建议是采用方法二的方式,速度更快整体文件更小。

修改读取模型的位置为模型的绝对路径:/Langchain-Chatchat/configs/model_config.py

(至少需要修改chatglm3-6b和bge-large-zh的模型路径)

保留你需要使用的模型图片:

图片

注意事项:不要直接git clone,无法clone大文件。对比hugging face上的文件大小,确保文件是完整的,不要漏下,issues上不少报错是由于文件没下载完整。

3.2.2 查看系统环境并安装cuda版本的pytorch

主要是查看系统cuda的版本及安装情况

(1)查看系统支持cuda最大版本,输入命令nvidia-smi:

图片

其中红线框中的数字表明此服务器中支持的最大cuda版本,因此服务器中可以安装此版本及以下版本。

(2)安装cuda版本的pytorch

# 以cuda11.3为例
!pip install torch==1.11.0+cu113 torchvision==0.12.0+cu113 torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cu113

图片

3.2.3 安装依赖requirements.txt

安装之前需要把文件中与torch有关的语句隐掉:

图片

输入命令安装相关依赖:

pip install -r requirements.txt

3.2.4 初始化知识库

如果是第一次运行本项目,知识库尚未建立,或者配置文件中的知识库类型、Embedding模型发生变化,需要以下命令初始化或重建知识库:

python init_database.py --recreate-vs

3.2.5 启动项目

启动前,确保已经按照参数配置正确配置各config模块。一键启动脚本 startup.py, 一键启动所有 Fastchat 服务、API 服务、WebUI 服务,示例代码:

python startup.py -a

可选参数包括 -a (或–all-webui), --all-api, --llm-api, -c (或–controller), --openai-api, -m (或–model-worker), --api, --webui,其中:

  • –all-webui 为一键启动 WebUI 所有依赖服务;
  • –all-api 为一键启动 API 所有依赖服务;
  • –llm-api 为一键启动 Fastchat 所有依赖的 LLM 服务;
  • –openai-api 为仅启动 FastChat 的 controller 和 openai-api-server 服务;

其他为单独服务启动选项。

若想指定非默认模型,需要用 --model-name 选项,示例:

$ python startup.py --all-webui --model-name Qwen-7B-Chat

3.3 部署过程中可能需要的问题

3.3.1 异常1:能打开页面但页面显示TypeError

报错描述:单独运行streamlit run webui.py时出现TypeError: ‘NoneType’ object is not iterable(能成功打开界面,但界面上显示报错)

具体报错:

File "/root/miniconda3/lib/python3.8/site-packages/streamlit/runtime/scriptrun
exec(code, module.dict)
File "/root/autodl-tmp/Langchain-Chatchat/webui.py", line 64, in
pages[selected_page]["func"](api=api, is_lite=is_lite)
File "/root/autodl-tmp/Langchain-Chatchat/webui_pages/dialogue/dialogue.py",
running models = list(api.list running models())
'NoneType' object is not iterable

图片

解决方案:关于该报错,github上的issues上很多人遇到,可能的解决方案有以下几种:

  • 因为没有运行后端,无法找到对应模型:解决方案是用python startup.py -a的方式一键启动所有 Fastchat 服务、API 服务、WebUI 服务,不要单独启动WebUI服务(虽然网上很多教程说可以单独启动)。
  • 关闭代理:由于内网问题,设置如下代理可能会报此错误,删除如下代码即可
  • 安装pip install httpx==0.25.0
  • 重启项目

3.3.2 异常2:API通信遇到错误

报错描述:首次对话没问题,继续对话出现API通信遇到错误

具体报错:

ERROR: RemoteProtocolError: API通信遇到错误:peer closed connection without sending complete message body (incomplete chunked read)

图片

解决方案:

主要是联网的问题,issues上有很多类似的报错

由于oppo在starfire上访问外网(如huggingface)需要加入下代码:

os.environ['http_proxy'] = 'http://nbproxy.mlp.oppo.local:8888'
os.environ['https_proxy'] = 'http://nbproxy.mlp.oppo.local:8888'

3.3.3 异常3:lib报错

报错描述:ImportError: libffi.so.6: cannot open shared object file: No such file or directory

解决方案:

sudo ln -s /usr/lib/x86_64-linux-gnu/libffi.so.6 /usr/lib/x86_64-linux-gnu/libffi.so.7
参考:https://stackoverflow.com/questions/61875869/ubuntu-20-04-upgrade-python-missing-libffi-so-6#;https://www.jianshu.com/p/20e92799504d

3.3.4 其他报错

ImportError: cannot import name ‘DEFAULT_CIPHERS’ from 'urllib3.util.ssl

pip install urllib3==1.25.11

cannot import name ‘ddg’ from ‘duckduckgo_search’

pip install duckduckgo-search==2.9.0 

AttributeError: ‘Chatbot’ object has no attribute ‘style’

!pip install gradio==3.48.0

AttributeError: module ‘lib’ has no attribute ‘X509_V_FLAG_CB_ISSUER_CHECK’

https://blog.youkuaiyun.com/weixin_45666566/article/details/133960436

pydantic.errors.PydanticUserError: If you use @root_validator with pre=False (the default) you MUST specify skip_on_failure=True. Note that @root_validator is deprecated and should be replaced with @model_validator.

pip install pydantic==1.10.13

ImportError: cannot import name ‘RetrievalQA’ from ‘langchain.chains’ (/opt/conda/lib/python3.7/site-packages/langchain/chains/init.py)

LangChain版本不对

KeyError: ‘st.session_state has no key “conversation_ids”. Did you forget to initialize it? More info: https://docs.streamlit.io/library/advanced-features/session-state#initialization’

打开webui应使用命令streamlit run webui.py,根据报错信息,推测使用的是命令 python webui.py 

libGL.so.1: cannot open shared object file: No such file or directory

pip install opencv-python-headless

RuntimeError: The NVIDIA driver on your system is too old (found version 11040). Please update your GPU driver by downloading and installing a new version from the URL: http://www.nvidia.com/Download/index.aspx Alternatively, go to: https://pytorch.org to install a PyTorch version that has been compiled with your version of the CUDA driver.

原因:pytorch和cuda版本不适配

问题排查:
NVIDIA-smi   # 查看GPU的版本
 # 看torch是否可用
import torch
print(torch.__version__)
print(torch.cuda.is_available())    # 如果返回False就是pytorch版本和cuda版本不匹配
解决方案:(根据对应的cuda版本安装pytorch对应版本)
pip install torch==1.11.0+cu113 torchvision==0.12.0+cu113 torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cu113

知识库页面的表格出

图片

解决方案:https://github.com/chatchat-space/Langchain-Chatchat/issues/2795

04 LangChain-Chatchat项目源码解析

4.1 技术路线图

  • Langchain 应用

    • 本地数据接入

      • 接入非结构化文档
      • 结构化数据接入
    • 分词及召回

      • 接入不同类型 TextSplitter
      • 优化依据中文标点符号设计的 ChineseTextSplitter
    • 搜索引擎接入

      • Bing 搜索
      • DuckDuckGo 搜索
      • Metaphor 搜索
    • Agent 实现

      • 基础React形式的Agent实现,包括调用计算器等
      • Langchain 自带的Agent实现和调用
      • 智能调用不同的数据库和联网知识
  • LLM 模型接入

    • 支持通过调用 FastChat api 调用 llm
    • 支持 ChatGLM API 等 LLM API 的接入
    • 支持 Langchain 框架支持的LLM API 接入
  • Embedding 模型接入

    • 支持调用 HuggingFace 中各开源 Emebdding 模型
    • 支持 OpenAI Embedding API 等 Embedding API 的接入
    • 支持 智谱AI、百度千帆、千问、MiniMax 等在线 Embedding API 的接入
  • 基于 FastAPI 的 API 方式调用

  • Web UI

    • 基于 Streamlit 的 Web UI

4.2 代码结构

图片

4.2.1 webui.py

由于startup.py是以子进程的方式启动各个服务,为了将各组件解耦,就需要为每个组件编写单独的启动脚本。

就webui.py来说,它主要对外提供webui界面,通过下面的命令启动。

streamlit run webui.py --server.address 0.0.0.0 --server.port 6006 --server.enableCORS=false --server.enableXsrfProtection=false

webui.py内部基于streamlit框架实现了用户交互界面,包括前端和后端两个部分。用python实现的后端部分代码存放在webui_pages/目录下。而前端代码则是由streamlit框架进行绘制。

4.2.2 文档数据上传

解析文件路径:server/knowledge_base/utils.py

各个解析方法加载路径:document_loaders/mypdfloader.py

Langchian-Chatchat中对于不同类型的文件提供了不同的处理方式:

LOADER_DICT = {"UnstructuredHTMLLoader": ['.html'],
               "UnstructuredMarkdownLoader": ['.md'],
               "CustomJSONLoader": [".json"],
               "CSVLoader": [".csv"],
               "RapidOCRPDFLoader": [".pdf"],
               "RapidOCRLoader": ['.png', '.jpg', '.jpeg', '.bmp'],
               "UnstructuredFileLoader": ['.eml', '.msg', '.rst',
                                          '.rtf', '.txt', '.xml',
                                          '.docx', '.epub', '.odt',
                                          '.ppt', '.pptx', '.tsv'],
               }

作者撰写了单独的方法解析各个文件:

FilteredCSVloader.py
mydocloader.py
myimgloader.py
mypdfloader.py
mypptloader.py
ocr.py

下面以PDF解析方式为例(加载器为RapidOCRPDFLoader),处理方式为:

  • 首先使用fitz(即pyMuPDF)的open方法解析PDF文件;
  • 然后对于每一页的文本内容,通过get_text方法进行获取,而对于图片内容通过get_images方法进行获取,获取后通过RapidOCR对图片中的文本内容进行提取;
  • 最后将从图片中提取的文本和原始的文本内容进行拼接,得到最终的所有文本内容。然后进行下一步的分词和文本切割。

4.2.3 文本切割

分词器支持LangChain定义的分词器和自定义的分词器。

LangChain定义的分词器有如下几种:

  • RecursiveCharacterTextSplitter():按字符串分割文本,递归地尝试按不同的分隔符进行分割文本。
  • CharacterTextSplitter():按字符来分割文本。
  • MarkdownHeaderTextSplitter():基于指定的标题来分割markdown 文件
  • TokenTextSplitter():按token来分割文本。
  • SentenceTransformersTokenTextSplitter() : 按token来分割文本
  • Language() - 用于 CPP、Python、Ruby、Markdown 等。
  • NLTKTextSplitter():使用 NLTK(自然语言工具包)按句子分割文本
  • SpacyTextSplitter() - 使用 Spacy按句子的切割文本。

自定义的分词器:

  • AliTextSplitter
  • ChineseRecursiveTextSplitter
  • ChineseTextSplitter

默认使用作者自定义的分词器ChineseRecursiveTextSplitter,主要是通过[“\n\n”,“\n”,“。|!|?”, “.\s|!\s|?\s”,“;|;\s”,“,|,\s”]进行分割。

4.2.3 中文标题增强

可以使用这些识别的标题来增强后续文档内容的上下文。

使用is_possible_title函数使用特定标准来确定文本是否是潜在标题。这些包括检查文本长度,检查文本是否以标点符号结尾,检查文本长度是否超过设定值等。若识别出文本可能为潜在的中文标题,那么会作为page_content加入。

doc.page_content = f"下文与({title})有关。{doc.page_content}"

05 LangChain-Chatchat构建知识问答

知识库对话功能的后端是在server/chat/knowledge_base_chat.py方法中实现的。

主要包括以下输入参数:

  • query:查询语句
  • knowledge_base_name:知识库名称
  • top_k:匹配向量数
  • score_threshold:知识库匹配相关度阈值,取值范围在0-2之间,SCORE越小,相关度越高,取到2相当于不筛选。
  • history:历史对话记录
  • model_name:LLM 模型名称
  • local_doc_url:是否返回原知识文件路径

整体构建步骤如下:

  1. 在方法内部,首先通过search_docs方法查询向量数据库,召回top_k个相似文档。
  2. 如果启用了reranker,则使用reranker对文档进行重新排序
  3. 然后通过换行符将docs拼接成context,这就是输入大模型的知识上下文。
  4. 如果没有找到相关的文档,则使用empty的prompt模板
  5. 随后使用模型代理和历史对话记录,初始化chain对象。最后将查询语句和上下文信息输入chain对象,通过asyncio库异步调用大模型进行处理。
  6. 末尾部分,将知识出处放入返回的json中。

5.1 从向量库中召回k个相似文档(search_docs)

# 在另一个线程中搜索相关文档
docs = await run_in_threadpool(search_docs,
                               query=query,
                               knowledge_base_name=knowledge_base_name,
                               top_k=top_k,
                               score_threshold=score_threshold) 

search_docs的具体实现方法:/server/knowledge_base/kb_service/faiss_kb_service.py,具体流程如下:

  1. 将用户问题query向量化;
  2. 通过向量库的similarity_search_with_score_by_vector函数,传入embeddings、top_k和score_threshold。(返回结果数由top_k确定,最小相似度分数由score_threshold确定)。其中计算相似度通过余弦相似度进行匹配,score含义(distance score):L2距离,因此score分数越小,代表二者越相近;
  3. 返回元组列表,每个元组包含一个Document对象和一个表示query和文档相似度的分数。
embeddings = embed_func.embed_query(query)
with self.load_vector_store().acquire() as vs:
    docs = vs.similarity_search_with_score_by_vector(embeddings, k=top_k, score_threshold=score_threshold)

LangChain-chatchat目前支持的向量数据库:(默认使用FAISS)

图片

5.2 如果启用了reranker,则使用reranker对文档进行重新排序

if USE_RERANKER:
    reranker_model_path = MODEL_PATH["reranker"].get(RERANKER_MODEL,"BAAI/bge-reranker-large")
    reranker_model = LangchainReranker(top_n=top_k,
                                    device=embedding_device(),
                                    max_length=RERANKER_MAX_LENGTH,
                                    model_name_or_path=reranker_model_path
                                    )
    docs = reranker_model.compress_documents(documents=docs,
                                             query=query)

reranker的流程如下所示:

  1. 创建句子对:创建一个由query(查询)和page_content(文档内容)组成的句子对
  2. sentence_pairs = [[query, _doc] for _doc in _docs]
  3. 使用模型进行预测:由bge-reranker模型返回一个得分,表示query与page_content的相关性results = self._model.predict(sentences=sentence_pairs)
  4. 选择前n个文档values, indices = results.topk(top_k)
  5. 为文档分配相关性得分,将其添加到文档的元数据中

关于启动reranker是否能提升性能,答案是不一定。

issues上大家的实践经验对rerank的优化性能褒贬不一,有的使用rerank后效果提升,有的未提升。原因可能如下:

  • 现有框架中,对于相似性越高的docs,会放在prompt的越前面,因为:“通过在开头放置更多相似的文档或上下文,该模型确保在生成响应时以更高的优先级考虑最相关的信息。“
  • 有相关的研究表明,越相似的docs应该放在prompt的越后面。因为:检索返回的条目在prompt中的顺序对结果有影响。我们发现把最相似的排在后面,指标获得比较大的提升。原因可能是,相关的条目排在后面,距离question更近,LLM模型不容易遗忘,并且更容易找出答案。

5.3 然后通过换行符将docs拼接成context,这就是输入大模型的知识上下文

context = "\n".join([doc.page_content for doc in docs])

如果没有找到相关的文档,则使用empty的prompt模板

"empty":  # 搜不到知识库的时候使用
    '请你回答我的问题:\n'
    '{{ question }}\n\n'

5.4 随后使用模型代理和历史对话记录,初始化chain对象

最后将查询语句和上下文信息输入chain对象,通过asyncio库异步调用大模型进行处理。

# 将用户输入转换为消息模板
input_msg = History(role="user", content=prompt_template).to_msg_template(False)
# 创建聊天提示
chat_prompt = ChatPromptTemplate.from_messages(
    [i.to_msg_template() for i in history] + [input_msg])
# 创建LLM链
chain = LLMChain(prompt=chat_prompt, llm=model)
# 开始一个在后台运行的任务
task = asyncio.create_task(wrap_done(
    chain.acall({"context": context, "question": query}),
    callback.done),
)

5.5 末尾部分,将知识出处放入返回的json中。

# 生成源文档列表
source_documents = []
for inum, doc in enumerate(docs):
    filename = doc.metadata.get("source")
    parameters = urlencode({"knowledge_base_name": knowledge_base_name, "file_name": filename})
    base_url = request.base_url
    url = f"{base_url}knowledge_base/download_doc?" + parameters
    text = f"""出处 [{inum + 1}] [{filename}]({url}) \n\n{doc.page_content}\n\n"""
    source_documents.append(text)
# 如果没有找到相关文档,则添加红色提示
if len(source_documents) == 0:
    source_documents.append(f"<span style='color:red'>未找到相关文档,该回答为大模型自身能力解答!</span>")

06 LangChain-Chatchat的prompt设计

主要包括有上下文提供和无上下文提供两种prompt:

"knowledge_base_chat": {
    "default":
        '<指令>根据已知信息,简洁和专业的来回答问题。如果无法从中得到答案,请说 “根据已知信息无法回答该问题”,'
        '不允许在答案中添加编造成分,答案请使用中文。 </指令>\n'
        '<已知信息>{{ context }}</已知信息>\n'
        '<问题>{{ question }}</问题>\n',


    "text":
        '<指令>根据已知信息,简洁和专业的来回答问题。如果无法从中得到答案,请说 “根据已知信息无法回答该问题”,答案请使用中文。 </指令>\n'
        '<已知信息>{{ context }}</已知信息>\n'
        '<问题>{{ question }}</问题>\n',


    "empty":  # 搜不到知识库的时候使用
        '请你回答我的问题:\n'
        '{{ question }}\n\n',
}

07 LangChain-Chatchat其他项

7.1 LangChain-Chatchat效果这么好了,还有什么可以优化的方向?

issues上很多人提到,想要该项目效果更好,可以做的就是使用文档整理格式+分词器设计,效果还可以。目前项目使用的Chinese分割方式可以处理所有文档,但效果并不好,issues上有很多人开发的分词方式。

7.2 为什么LangChain-Chatchat速度这么快?

图片

总结

本篇主要介绍了LangChain-Chatchat项目,可用于构建离线部署的检索增强生成 (RAG) 大模型的本地知识库问答应用项目,分别从项目介绍、实现原理、部署流程、源码解析以及构建知识问答等详细介绍了LangChain-Chatchat项目。对于想基于本地知识库构建知识问答项目的小伙伴可能有帮助。

如何学习AI大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值