从零手搓一个 RAG智能体,抱紧大模型时代的“大腿”!(深入学习之内附超详细步骤及代码)



🤔 还在为大模型的“胡说八道”头疼? 🤔

你是否也遇到过这样的情况:满怀期待地向大模型(LLM)提问,得到的却是些驴唇不对马嘴的答案? 🤯 这可不是你提问的姿势不对,而是大模型“一本正经地胡说八道”了,也就是所谓的“幻觉”(Hallucination)。

别担心,你不是一个人!大模型虽强,但也有“软肋”:

*   脑子”里的知识会过时:就像我们不常更新自己的知识库一样,大模型也可能对新信息“一脸懵”。
*   偏科”严重:面对特定领域的专业问题,大模型可能就“抓瞎”了。
*   逻辑”有时会掉线:大模型在推理方面还有待加强,有时会给出些“匪夷所思”的结论。

 



😵 难道我们就只能对大模型的“幻觉”束手无策了吗? 😵

当然不是!今天,我们就来一起揭秘 AI 时代的“新宠”——检索增强生成技术(Retrieval-Augmented Generation,RAG),让你的大模型从此告别“幻觉”,变身“靠谱小助手”!

🚀 RAG:给大模型装上“外挂”,让它更懂你! 🚀

如果把大模型比作一个“学霸”,那么 RAG 就像是给这位“学霸”配备了一位“超级图书管理员”和一本“百科全书”。

这位“图书管理员”神通广大,能在海量文档中迅速找到“学霸”需要的知识(检索过程)。而这本“百科全书”则包罗万象,涵盖了各种领域的专业知识(文档数据库)。

有了这两样“神器”的加持,“学霸”在回答问题时,不仅能引经据典,还能结合最新信息,给出更准确、更全面的答案。

简单来说,RAG 的工作原理就是:

1.  提问”:你向大模型提出一个问题(Query)。
2.  检索”:“图书管理员”根据你的问题,从“百科全书”中找到相关的知识片段。
3.  生成”:“学霸”结合“图书管理员”提供的知识片段,生成更靠谱的答案。

有了RAG,大模型就能:

*   告别“幻觉”:有了“参考答案”,再也不怕胡说八道了。
*   及时更新知识:随时获取最新信息,告别“落伍”。
*   变身“专家”:轻松应对各领域专业问题。
*   推理更“靠谱”:有理有据,结论更可信。

🎉 是不是很神奇?想不想亲手打造一个属于自己的 RAG? 🎉

别急,跟着我一步步来,你也能轻松掌握这项“黑科技”!

🛠️ RAG 的“五脏六腑”:核心模块大揭秘! 🛠️

要构建一个 RAG,我们需要准备以下几个核心模块:

1.  向量化模块:将文档片段转换成计算机能理解的“数字指纹”(向量)。
2.  文档加载和切分模块:将各种格式的文档(如 PDF、Markdown、TXT 等)整理成“知识碎片”。
3.  数据库:存储“知识碎片”及其对应的“数字指纹”。
4.  检索模块:根据你的问题,找出最相关的“知识碎片”。
5.  大模型模块:结合检索到的“知识碎片”,生成最终答案。

 



不用担心这些模块听起来很复杂,其实它们就像乐高积木一样,只要按照一定的规则组合起来,就能搭建出一个强大的 RAG 系统。

💡 RAG 的“工作流程”:三步走战略! 💡

RAG 的工作流程可以概括为三个步骤:

1.  索引:将文档库切分成小块(Chunk),并为每个小块生成“数字指纹”(向量索引)。
2.  检索:根据你的问题,找到“数字指纹”最相似的文档片段。
3.  生成:大模型根据检索到的文档片段,生成最终答案。

 



🤓 手把手教你撸代码:从零开始构建 RAG! 🤓

接下来,我们将一步步实现 RAG 的各个核心模块,让你对 RAG 的内部运作机制有更深入的了解。

1️⃣ 向量化:给文档片段打上“数字指纹”

向量化模块的核心任务是将文本转换成向量。为了方便扩展和使用不同的模型,我们先定义一个向量化基类(BaseEmbeddings):

 

class BaseEmbeddings:
    """
    Base class for embeddings
    """
    def __init__(self, path: str, is_api: bool) -> None:
        self.path = path
        self.is_api = is_api
   
    def get_embedding(self, text: str, model: str) -> List[float]:
        raise NotImplementedError
   
    @classmethod
    def cosine_similarity(cls, vector1: List[float], vector2: List[float]) -> float:
        """
        calculate cosine similarity between two vectors
        """
        dot_product = np.dot(vector1, vector2)
        magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2)
        if not magnitude:
            return 0
        return dot_product / magnitude

这个基类定义了两个方法:

*   `get_embedding`:获取文本的向量表示(具体实现由子类完成)。

*   `cosine_similarity`:计算两个向量的余弦相似度(用于衡量文档片段与问题的相关性)。

有了基类,我们就可以轻松实现各种 Embedding 模型,比如 OpenAI 的 Embedding API:

class OpenAIEmbedding(BaseEmbeddings):
    """
    class for OpenAI embeddings
    """
    def __init__(self, path: str = '', is_api: bool = True) -> None:
        super().__init__(path, is_api)
        if self.is_api:
            from openai import OpenAI
            self.client = OpenAI()
            self.client.api_key = os.getenv("OPENAI_API_KEY")
            self.client.base_url = os.getenv("OPENAI_BASE_URL")
   
    def get_embedding(self, text: str, model: str = "text-embedding-3-large") -> List[float]:
        if self.is_api:
            text = text.replace("\n", " ")
            return self.client.embeddings.create(input=[text], model=model).data[0].embedding
        else:
            raise NotImplementedError



2️⃣ 文档加载和切分:把“大部头”变成“小纸条”

我们需要一个能处理各种文档格式(如 PDF、Markdown、TXT)的工具,并将它们切分成大小合适的“知识碎片”。

 

def read_file_content(cls, file_path: str):
    # 根据文件扩展名选择读取方法
    if file_path.endswith('.pdf'):
        return cls.read_pdf(file_path)
    elif file_path.endswith('.md'):
        return cls.read_markdown(file_path)
    elif file_path.endswith('.txt'):
        return cls.read_text(file_path)
    else:
        raise ValueError("Unsupported file type")



切分文档时,要注意以下几点:

*   长度适中:太长了会影响检索效率,太短了又可能丢失关键信息。
*   内容重叠:相邻的“知识碎片”之间最好有一些重叠,以保证检索的连贯性。
*   句子完整:尽量以句子为单位进行切分,避免“断章取义”。

 

def get_chunk(cls, text: str, max_token_len: int = 600, cover_content: int = 150):
    chunk_text = []

    curr_len = 0
    curr_chunk = ''

    lines = text.split('\n')  # 假设以换行符分割文本为行

    for line in lines:
        line = line.replace(' ', '')
        line_len = len(enc.encode(line))
        if line_len > max_token_len:
            print('warning line_len = ', line_len)
        if curr_len + line_len <= max_token_len:
            curr_chunk += line
            curr_chunk += '\n'
            curr_len += line_len
            curr_len += 1
        else:
            chunk_text.append(curr_chunk)
            curr_chunk = curr_chunk[-cover_content:]+line
            curr_len = line_len + cover_content

    if curr_chunk:
        chunk_text.append(curr_chunk)

    return chunk_text



3️⃣ 数据库与向量检索:找到最相关的“知识碎片”

我们需要一个“仓库”来存储“知识碎片”及其“数字指纹”,并能根据问题快速找到最相关的“碎片”。

 

class VectorStore:
    def __init__(self, document: List[str] = ['']) -> None:
        self.document = document

    def get_vector(self, EmbeddingModel: BaseEmbeddings) -> List[List[float]]:
        # 获得文档的向量表示
        pass

    def persist(self, path: str = 'storage'):
        # 数据库持久化,本地保存
        pass

    def load_vector(self, path: str = 'storage'):
        # 从本地加载数据库
        pass

    def query(self, query: str, EmbeddingModel: BaseEmbeddings, k: int = 1) -> List[str]:
        # 根据问题检索相关的文档片段
        pass

`query` 方法是向量检索的核心:

def query(self, query: str, EmbeddingModel: BaseEmbeddings, k: int = 1) -> List[str]:
    query_vector = EmbeddingModel.get_embedding(query)
    result = np.array([self.get_similarity(query_vector, vector)
                        for vector in self.vectors])
    return np.array(self.document)[result.argsort()[-k:][::-1]].tolist()



4️⃣ 大模型模块:生成最终答案

最后,我们需要一个大模型来根据检索到的“知识碎片”生成答案。

 

class BaseModel:
    def __init__(self, path: str = '') -> None:
        self.path = path

    def chat(self, prompt: str, history: List[dict], content: str) -> str:
        pass

    def load_model(self):
        pass

以 InternLM2-chat-7B 模型为例:

class InternLMChat(BaseModel):
    def __init__(self, path: str = '') -> None:
        super().__init__(path)
        self.load_model()

    def chat(self, prompt: str, history: List = [], content: str='') -> str:
        prompt = PROMPT_TEMPLATE['InternLM_PROMPT_TEMPALTE'].format(question=prompt, context=content)
        response, history = self.model.chat(self.tokenizer, prompt, history)
        return response


    def load_model(self):
        import torch
        from transformers import AutoTokenizer, AutoModelForCausalLM
        self.tokenizer = AutoTokenizer.from_pretrained(self.path, trust_remote_code=True)
        self.model = AutoModelForCausalLM.from_pretrained(self.path, torch_dtype=torch.float16, trust_remote_code=True).cuda()

为了方便管理,我们可以用一个字典来保存所有的 prompt:

PROMPT_TEMPLATE = dict(
    InternLM_PROMPT_TEMPALTE="""先对上下文进行内容总结,再使用上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。
        问题: {question}
        可参考的上下文:
        ···
        {context}
        ···
        如果给定的上下文无法让你做出回答,请回答数据库中没有这个内容,你不知道。
        有用的回答:"""
)



大功告成:见证奇迹的时刻!

现在,我们可以将所有模块组合起来,运行一个完整的 RAG Demo:

 

from RAG.VectorBase import VectorStore
from RAG.utils import ReadFiles
from RAG.LLM import OpenAIChat, InternLMChat
from RAG.Embeddings import JinaEmbedding, ZhipuEmbedding


# 没有保存数据库
docs = ReadFiles('./data').get_content(max_token_len=600, cover_content=150) # 获得data目录下的所有文件内容并分割
vector = VectorStore(docs)
embedding = ZhipuEmbedding() # 创建EmbeddingModel
vector.get_vector(EmbeddingModel=embedding)
vector.persist(path='storage') # 将向量和文档内容保存到storage目录下,下次再用就可以直接加载本地的数据库

question = 'git的原理是什么?'

content = vector.query(question, model='zhipu', k=1)[0]
chat = InternLMChat(path='model_path')
print(chat.chat(question, [], content))

我们也可以从本地加载已有的数据库:

from RAG.VectorBase import VectorStore
from RAG.utils import ReadFiles
from RAG.LLM import OpenAIChat, InternLMChat
from RAG.Embeddings import JinaEmbedding, ZhipuEmbedding

# 保存数据库之后
vector = VectorStore()

vector.load_vector('./storage') # 加载本地的数据库

question = 'git的原理是什么?'

embedding = ZhipuEmbedding() # 创建EmbeddingModel

content = vector.query(question, EmbeddingModel=embedding, k=1)[0]
chat = InternLMChat(path='model_path')
print(chat.chat(question, [], content))



👏 恭喜你,已经成功构建了一个属于自己的 RAG 系统! 👏

现在,你可以尽情地向它提问,感受它强大的知识整合能力和精准的回答能力吧!

📚 总结:RAG 的核心要点回顾 📚

让我们再来回顾一下 RAG 的核心要点:

*   五大模块:向量化、文档加载和切分、数据库、向量检索、大模型。
*   三步流程:索引、检索、生成。
*   核心优势:告别“幻觉”、知识及时更新、变身“专家”、推理更“靠谱”。

有了 RAG,大模型不再是“空中楼阁”,而是真正成为了我们工作和生活中的“得力助手”。

🚀 未来已来,让我们一起拥抱 RAG,探索大模型时代的无限可能! 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值