【RAG+向量数据库】小白从0构建一个rag和向量数据库demo

使用工具

  1. langchain框架
  2. bge-small-zh-v1.5 嵌入模型
  3. Chroma向量库
  4. LCEL的RAG链

整体过程

构建向量库

首先了解整个过程

  1. 收集数据:txt,pdf等数据格式均可以
  2. 加载数据,可以利用DirectoryLoader加载,但是文章使用的直接遍历加载
  3. 加载embeding模型,本文章使用的是bge-small-zh-v1.5模型
  4. 使用该模型进行向量化
  5. 储存到我们的chroma向量库

查询知识库

  1. 加载llm模型
  2. 加载上面一样的embedding模型
  3. 加载储存好的向量数据库
  4. 从数据库中创建一个检索器
  5. 定义prompt模板
  6. 利用lcel的rag链将他们连一起
  7. 示例问题

详细过程

构建知识库

  1. 导入必要库
python 复制代码
import os
from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_huggingface import HuggingFaceEmbeddings

2.下载模型

bash 复制代码
git install lfs
$env:GIT_CLONE_PROTECTION_ACTIVE="false"; git clone https://huggingface.co/BAAI/bge-small-zh-v1.5
  1. 选择embeding模型 下面的path需要更换为自己的path,也就是提前下载好path
python 复制代码
model_kwargs = {'device': 'cpu'} # 如果你有支持CUDA的NVIDIA显卡,可以改成 {'device': 'cuda'}
encode_kwargs = {'normalize_embeddings': True} # 设置为True,返回归一化的向量,便于余弦相似度计算
embedding_function = HuggingFaceEmbeddings(
    model_name=MODEL_PATH,
    model_kwargs=model_kwargs,
    encode_kwargs=encode_kwargs
)
  1. 加载文档 在加载文档的过程中,我们采用document的构建方式,是因为对于每一个获取到的文件,我们都用document进行格式化
  • page_content (str): 文档的核心文本内容。
  • metadata (dict): 描述这份文档的元数据,比如来源文件名、页码、作者等等。这个元数据在后续的高级应用中非常有用。 目前支持两种格式,当然可拓展性很高
python 复制代码
documents = []
    try:
        for root, _, files in os.walk(SOURCE_DIRECTORY):
            print(f"正在扫描文件夹: {root}")
            for file in files:
                file_path = os.path.join(root, file)
                if file.endswith(".txt"):
                    try:
                        with open(file_path, 'r', encoding='utf-8') as f:
                            text = f.read()
                        doc = Document(page_content=text, metadata={"source": file_path})
                        documents.append(doc)
                        print(f"成功手动加载 TXT 文件: {file_path}")
                    except Exception as e:
                        print(f"加载 TXT 文件 {file_path} 时出错: {e}")

                elif file.endswith(".pdf"):
                    try:
                        # 使用LangChain的PyPDFLoader来处理PDF
                        # 它会为PDF的每一页创建一个Document对象
                        pdf_loader = PyPDFLoader(file_path)
                        # .load()返回一个Document列表
                        pdf_docs = pdf_loader.load()
                        documents.extend(pdf_docs) # 将PDF中的所有页面文档添加到主列表中
                        print(f"成功加载 PDF 文件: {file_path} (共 {len(pdf_docs)} 页)")
                    except Exception as e:
                        print(f"加载 PDF 文件 {file_path} 时出错: {e}")
  1. 然后将加载的文档分成小块
  • chunk_size=500: 这定义了每个文本块(Chunk)的最大长度,单位是字符。它会尽量保证每个块不超过500个字符。
  • chunk_overlap=100: 这定义了相邻两个文本块之间的重叠字符数。也就是说,下一个块的开头会包含上一个块结尾的100个字符。
  • 而用到的方法:它会尝试按一个优先级列表来进行分割,这个列表默认是 ["\n\n", "\n", " ", ""]。它会首先尝试用段落(两个换行符)来分割,如果分完的块还是太大,它就会退一步,在块内用句子(一个换行符)来分割,再不行就用空格,最后才会粗暴地按字符分割。这比简单的按字符分割效果好得多。
python 复制代码
 # 2. 将加载的文档切分成小块 (Chunking)
    # 这对于后续的检索至关重要
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
    split_docs = text_splitter.split_documents(documents)
    print(f"文档已切分为 {len(split_docs)} 个小块。")
  1. 向量化到ChromaDB中
python 复制代码
 db = Chroma.from_documents(
        split_docs,
        embedding_function,
        persist_directory=PERSIST_DIRECTORY  # 指定路劲
    )
    # 确保数据被写入磁盘
    db.persist()

查询向量库

  1. 加载llm模型 这里自行加载储存的密码
python 复制代码
secrets = load_secrets()
llm = ChatOpenAI(
    api_key=secrets.get("API_KEY"),
    base_url=secrets.get("BASE_URL"),
    model_name=secrets.get("MODEL"),
    temperature=0.0 # 对于RAG,我们希望答案基于事实,所以温度设为0
)
  1. 加载模型 与上一步一致
python 复制代码
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': True}
embedding_function = HuggingFaceEmbeddings(
    model_name=MODEL_PATH,
    model_kwargs=model_kwargs,
    encode_kwargs=encode_kwargs
)
  1. 加载数据库
python 复制代码
db = Chroma(
    persist_directory=PERSIST_DIRECTORY,
    embedding_function=embedding_function
)
  1. 创建一个检索器

它将一个完整的向量数据库(db对象)转换成了一个专门负责检索的"检索器"(Retriever对象) k: 3意味着:"当进行检索时,请返回与查询最相似的前3个文档块"

python 复制代码
retriever = db.as_retriever(search_kwargs={"k": 3}) # 设置为返回最相关的3个结果
  1. 定义prompt模板 利用langchain中的模板进行攥写
python 复制代码
template = """
请只根据以下提供的上下文信息来回答问题。

上下文:
{context}

问题:
{question}
"""
prompt = ChatPromptTemplate.from_template(template)
  1. 利用LCEL链构建rag链
  • 其中的retriever是前面的查询向量库,
  • RunnablePassthrough将问题传入
  • | prompt: 上一步的字典被"管道"传给prompt对象。prompt对象接收到这个字典,并用里面的context和question值来填充它的模板,生成一个结构化的Prompt。
  • | llm: 填充好的Prompt被传给llm对象,LLM执行并返回一个AIMessage对象。
  • | StrOutputParser(): AIMessage对象被传给StrOutputParser,它会提取出其中的文本内容,最终返回一个干净的字符串。
python 复制代码
rag_chain = (
    # RunnablePassthrough()会将问题同时传给retriever和prompt
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser() # 将LLM的输出解析为字符串
)

7 . 回答 invoke是LCEL链条的"执行"方法。它是触发整个链条从头到尾运行的命令。 当传入question的时候,就会执行整一个链

python 复制代码
response = rag_chain.invoke(question)

效果

txt:因果推断是统计学和数据科学中的一个核心领域,它的主要目标是确定一个事件(原因)对另一个事件(结果)的影响程度。这不仅仅是发现变量之间的相关性,而是要探究它们之间是否存在真正的因果关系。

一个经典的概念是"相关不等于因果"。例如,夏天的冰淇淋销量和溺水人数都同时上升,它们高度相关,但我们不能说是吃冰淇淋导致了溺水。这背后其实有一个共同的原因,即"炎热的天气",这个变量被称为"混杂变量"(Confounding Variable)。

处理混杂变量是因果推断的关键挑战之一。常用的方法包括:

  1. 随机对照试验(RCT):这是黄金标准,通过随机分组来消除混杂因素的影响。
  2. 工具变量法(Instrumental Variables):当无法进行RCT时,寻找一个"工具变量",它只影响原因而不直接影响结果。
  3. 回归不连续性设计(Regression Discontinuity):利用一个临界值来近似实现随机分组的效果。

RAG: 用户问题: 因果推断是什么?它和相关性有什么区别? --- RAG响应 --- 因果推断是统计学和数据科学中的一个核心领域,它的主要目标是确定一个事件(原因)对另一个事件(结果)的影响程度。它不仅仅是发现变量之间的相关性,而是要探究它们之间是否存在真正的因果关系。

与相关性相比,因果推断的区别在于:

  1. 相关性指的是两个变量之间是否存在统计上的关联,即它们是否经常一起变化。例如,夏天的冰淇淋销量和溺水人数 可能同时上升,这表明它们之间存在相关性。

  2. 因果关系则进一步探究一个变量是否真正导致了另一个变量的变化。例如,虽然夏天的冰淇淋销量和溺水人数可能同 时上升,但因果推断会探究是否是吃冰淇淋导致了溺水,而不是其他共同的原因,如炎热的天气。

简而言之,相关性描述了变量之间的统计关联,而因果推断则试图确定一个变量是否是另一个变量的原因。

用户问题: 处理混杂变量的方法有哪些? --- RAG响应 --- 处理混杂变量的方法包括:

  1. 随机对照试验(RCT):这是黄金标准,通过随机分组来消除混杂因素的影响。
  2. 工具变量法(Instrumental Variables):当无法进行RCT时,寻找一个"工具变量",它只影响原因而不直接影响结果。
  3. 回归不连续性设计(Regression Discontinuity):利用一个临界值来近似实现随机分组的效果。

用户问题: Judea Pearl是谁? --- RAG响应 --- 根据提供的知识库,我无法回答该问题。

相关推荐
梦想blog38 分钟前
DeepSeek + AnythingLLM 搭建你的私人知识库
ai·大模型·llm·anythingllm·deepseek
大模型开发7 小时前
Cursor 快速入门指南:从安装到核心功能
llm·agent·cursor
聚客AI8 小时前
⚡ 突破LLM三大局限:LangChain架构核心解析与最佳实践
人工智能·langchain·llm
czkm9 小时前
苹果🍎的奇幻漂流,当你提问后,ChatGPT在“想”什么?
chatgpt·llm
AI大模型11 小时前
大模型炼丹术(二):从离散的token IDs到具有语义信息的embedding
程序员·llm
AI大模型14 小时前
大模型炼丹术(一):从tokenizer说起,为LLM自回归预训练准备数据集
程序员·llm·agent
董厂长20 小时前
langchain :记忆组件混淆概念澄清 & 创建Conversational ReAct后显示指定 记忆组件
人工智能·深度学习·langchain·llm
G皮T1 天前
【人工智能】ChatGPT、DeepSeek-R1、DeepSeek-V3 辨析
人工智能·chatgpt·llm·大语言模型·deepseek·deepseek-v3·deepseek-r1
雷羿 LexChien1 天前
从 Prompt 管理到人格稳定:探索 Cursor AI 编辑器如何赋能 Prompt 工程与人格风格设计(上)
人工智能·python·llm·编辑器·prompt