当前位置:   article > 正文

自己手写了一个大模型RAG项目-04.知识库构建_rag自己的

rag自己的

大家好,我是程序锅。

github上的代码封装程度高,不利于小白学习入门。

常规的大模型RAG框架有langchain等,但是langchain等框架源码理解困难,debug源码上手难度大。

因此,我写了一个人人都能看懂、人人都能修改的大模型RAG框架代码。

整体项目结构如下图所示:手把手教你大模型RAG框架架构

手把手教你大模型RAG框架架构

整个小项目分为10个章节,和github高度封装的RAG代码不同,我们将从0到1搭建大模型RAG问答系统,所有代码评论区回复rag免费获取!

前序文章:
3.项目环境依赖按照

本篇文章将介绍4.知识库构建,知识库构建算是大模型RAG领域最核心的部分了(见图1),主要由知识存储、知识处理、知识向量化、知识检索和知识比较四大部分构成。不过,我们也可以从数据流转角度出发,涉及文件预处理、文件切分、向量化、构建索引等部分。

图1.RAG技术路线

一、文件预处理

输入:专业知识文件(pdf、word、txt等)

输出:结构化的json文件

在构建一个高效的RAG系统时,首要步骤是准备知识文档。现实场景中,我们面对的知识源可能包括多种格式,如Word文档、TXT文件、CSV数据表、Excel表格,甚至是PDF文件、图片和视频等。因此,第一步需要使用专门的文档加载器(例如PDF提取器)或多模态模型(如OCR技术),将这些丰富的知识源转换为大语言模型可理解的纯文本数据。

例如,处理PDF文件时,可以利用PDF提取器抽取文本内容;对于图片和视频,OCR技术能够识别并转换其中的文字信息。

文件预处理如果考虑全面,会涉及很多很多工作。比如你的pdf怎么解析、excel表格如何解析、如何去清洗*%@等垃圾字符等等。文件预处理模块很大程度会决定知识库的丰富程度。

在这个小项目中,为了每个人都能看懂并且能够修改。只涉及pdf、md、txt文件的纯文本解析。

import PyPDF2
import markdown
import html2text
import json
from tqdm import tqdm
import tiktoken
from bs4 import BeautifulSoup
import re
import os
    def read_pdf(cls, file_path: str):
        # 读取PDF文件
        with open(file_path, 'rb') as file:
            reader = PyPDF2.PdfReader(file)
            text = ""
            for page_num in range(len(reader.pages)):
                text += reader.pages[page_num].extract_text()
            return text

    def read_markdown(cls, file_path: str):
        # 读取Markdown文件
        with open(file_path, 'r', encoding='utf-8') as file:
            md_text = file.read()
            html_text = markdown.markdown(md_text)
            # 使用BeautifulSoup从HTML中提取纯文本
            soup = BeautifulSoup(html_text, 'html.parser')
            plain_text = soup.get_text()
            # 使用正则表达式移除网址链接
            text = re.sub(r'http\S+', '', plain_text) 
            return text

    def read_text(cls, file_path: str):
        # 读取文本文件
        with open(file_path, 'r', encoding='utf-8') as file:
            return file.read()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

二、文件切分

文件预处理后,我们还需执行一项关键步骤:文档切片。我们需要将文档分割成多个文本块,以便更高效地处理和检索信息。

首先为什么分割成多个文本块呢?

由于embedding模型对输入token有限制,因此只能分割成多个文本块,才能满足embedding模型的需求。

有人就会说:如果不考虑embedding模型,分块是越大越好吗?

不是。在分块的时候,也需要尽可能减少嵌入内容中的噪声,从而更有效地找到与用户查询最相关的文档部分。如果分块太大,可能包含太多不相关的信息,从而降低了检索的准确性。

因此,文件切分后的段落大小,必须有一个最大token长度max_token_len和最小token长度min_token_len。其中max_token_len是为了满足embedding模型对输入token的限制,也需减少嵌入内容中的噪声;min_token_len是为了要求切分后的段落不能太小,切分后的段落太小,可能会丢失必要的上下文信息,导致生成的回应缺乏连贯性或深度。

下面直接上代码:

 def get_chunk2(cls, text: str, min_token_len: int = 600, max_token_len: int = 1600, cover_content: int = 150):
        chunk_text = []
        chunk_lenth = []

        curr_len = 0
        curr_chunk = ''

        lines = text.split('\n')  # 假设以换行符分割文本为行
        for line in lines:
            line = line.replace(' ', '')
            line_len = len(enc.encode(line))
            if curr_len > max_token_len:
                while (curr_len > max_token_len):
                    split_a = enc.encode(curr_chunk)[:max_token_len]
                    split_b = enc.encode(curr_chunk)[max_token_len:]
                    curr_chunk = enc.decode(split_a)
                    chunk_text.append(curr_chunk)
                    chunk_lenth.append(max_token_len)
                    curr_chunk = curr_chunk[-cover_content:] + enc.decode(split_b)
                    curr_len = cover_content + curr_len - max_token_len
            else:
                if (curr_len <= min_token_len):
                    curr_chunk += line
                    curr_chunk += '\n'
                    curr_len += line_len
                    curr_len += 1
                else:
                    chunk_text.append(curr_chunk)
                    chunk_lenth.append(curr_len)
                    curr_chunk = curr_chunk[-cover_content:] + line
                    curr_len = line_len + cover_content
        if curr_chunk:
            chunk_text.append(curr_chunk)
            chunk_lenth.append(curr_len)
        return chunk_text
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

三、向量化

什么是向量化?

举个例子,设计4个句子,其中3个与大模型相关,1个与java相关。段落通过向量化后,使得与大模型相关的段落会在向量空间中距离比较近。java和大模型没那么相关,因此在向量空间中离得较远。

回到主题上来。长文本呢切分后需要给文本块做向量化。这里需要明确一点,知识库存的是向量化后的文本块。如下图示例:

由上面这一个示意图可以看出,知识库本质上就是一个列表,其中包含多个维度相同的向量。比如我们这个小项目采用智谱的embedding模型,模型编码为:embedding-2,向量化后的维度为1024。

代码如下:

import zhipu
class ZhipuEmbedding(BaseEmbeddings):
    """
    class for Zhipu embeddings
    """
    def __init__(self, path: str = '', is_api: bool = True, embedding_dim = 1024) -> None:
        super().__init__(path, is_api)
        if self.is_api:
            from zhipuai import ZhipuAI
            self.client = ZhipuAI(api_key=os.getenv("ZHIPUAI_API_KEY")) 
        self.embedding_dim = embedding_dim


    def get_embedding(self, text: str) -> List[float]:
        response = self.client.embeddings.create(
        model="embedding-2",
        input=text,
        )
        return response.data[0].embedding
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

四、构建索引

好了现在我们已经得到了知识库,我们需要思考一个问题。

问题向量与知识库匹配,是不是很慢?

答案肯定是的,如果不构建任何索引,非常简单粗暴的方法就是写一个for循环,挨个比较。

为了演示简单,暂时没有构建索引。后续文章会介绍如何采用Milvus 、Faiss等向量知识库技术改进构建索引部分,加快向量搜索。

所有代码评论区回复rag免费获取!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/508172
推荐阅读
相关标签
  

闽ICP备14008679号