RAG技术与Embedding模型实战:程序员必学指南,建议收藏!

一、【一问一答】

Q:输入不同长度的文本,为什么通过embedding后维度是一致?

维度是一样的,虽然输入文字长短不一但是embedding是按照语义来最终输出维度一样的向量,维度一样是为了方便做计算

Q:bge模型最低需要什么配置才能跑通

模型文件文件大小是2.3GB,CPU也可以跑通,或者显存 >=4GB

Q:RAG怎么在Linux环境下部署?如:py脚本做的faiss

和windows差不多,同样需要先 pip install 所依赖的安装包,然后运行对应的xx.py。也可以在Linux中的jupyter运行xx.ipynb

Q:pip install gensim 时一直报错

可能和依赖的python其他安装包的版本相关

Q:Embedding之后是不是会有许多向量一样?向量一样是语义相似?

语义是否类似,是将输入内容通过Embedding转化为相同维度的向量,根据向量之间的相似度进行计算来判定语义相似

Q:公司资料库中有很多技术文档、标准等pdf,包含很多表格。大模型回答问题时需要精确数据,RAG效果是不是不太好?有什么解决方案吗?

用MinerU,可以将pdf中的表格、图片 转化为 markdown格式。MinerU 是一款开源的 PDF 结构化解析工具,由 OpenDataLab 开源社区主导。
MinerU核心功能:把 PDF(含扫描版)快速转换成 Markdown、JSON、数据库等可直接使用的结构化数据,保留文本、表格、公式、图片和排版信息。一句话:上传 PDF,秒得可编辑的结构化文档,支持在线即用、本地脚本、Docker 批量三种方式。

https://mineru.net/

Q:语音可以转到知识库吗

先将语音转化为文本=> 然后Embedding

Q:pip install faiss-cpu, 如何装gpu版本

conda install -c conda-forge faiss-gpu

Q: 客户需要语音识别为文字,文字转换成代码,最后执行代码,专家推荐SFT而不是RAG,这是出于什么考量啊?

一般的逻辑是:先RAG,RAG解决不了的再SFT

客户嫌弃COT太长(Chain-of-Thought(思维链/推理链))。比如:在客服工单、邮件或聊天里,系统把完整的内部推理步骤(COT)全部呈现给了客户。客户嫌内容冗长、阅读负担大,只想看“结论/解决方案”,不想看一步步推导。简言之:客户嫌“废话太多,直接告诉我结果”

在这里插入图片描述
Q:大模型应用开发3种模式,提示工程VS RAG VS 微调,什么时候使用?

通过海量知识训练出LLM大模型,当我们提问时,大模型LLM回答错误。(1)第1个环节:我们先审查一下,我们提问的问题是否清晰明了。若是问题本身没有提问清楚,可以修改一下提示词。(2)第二个环节:若是问题非常清晰,但是LLM大模型没有回答正确,尝试看一下LLM是不是对某个公司特定的产品不了解(不知道某个会议纪要,某个领导的讲话),将资料数据做成RAG给到LLM(3)第三环节:若是一、二环节都不能回答正确,根本性原因是LLM的能力不足,通过微调SFT让模型具备某些能力(中医药大模型LLM,需要对中医药系统性的有了解和学习)。对于业务人员而言最常见的是“提示词工程”,因为不需要写代码,直接问就行;对于具备一定开发能力的人员通常会使用“RAG知识库”,需要将一些背景资料(COT思维链)给到LLM

二、RAG简介

1、RAG是什么

RAG(Retrieval-Augmented Generation,检索增强生成)是一种先检索、后生成的端到端技术:系统收到问题后,立即从外部知识库或互联网实时拉取最相关、最新的文档片段,将其与原问题一起输入大模型作为动态上下文,模型再基于这些“新鲜证据”生成答案,从而把传统大模型“静态记忆”升级为“实时外挂硬盘”。

2、RAG的优势是什么

• 解决知识时效性问题:大模型的训练数据通常是静态的,无法涵盖最新信息,而RAG可以检索外部知识库实时更新信息

• 减少模型幻觉:通过引入外部知识,RAG能够减少模型生成虚假或不准确内容的可能性

• 提升专业领域回答质量:RAG能够结合垂直领域的专业知识库,生成更具专业深度的回答

3、RAG的核心原理与流程

第一步——数据预处理

• 知识库构建:收集并整理文档、网页、数据库等多源数据,构建外部知识库

• 文档分块:将文档切分为适当大小的片段(chunks),以便后续检索。分块策略需要在语义完整性与检索效率之间取得平衡

• 向量化处理:使用嵌入模型(如BGE、M3E、Chinese-Alpaca-2等)将文本块转换为向量,并存储在向量数据库中

第二步——检索阶段

• 查询处理:将用户输入的问题转换为向量,并在向量数据库中进行相似度检索,找到最相关的文本片段

• 重排序:对检索结果进行相关性排序,选择最相关的片段作为生成阶段的输入

第三步——生成阶段

• 上下文组装:将检索到的文本片段与用户问题结合,形成增强的上下文输入

• 生成回答:大语言模型基于增强的上下文生成最终回答

4、RAG 的“毛坯房”版NativeRAG

NativeRAG(也称 Naive RAG / Baseline RAG)是检索增强生成的“最原始形态”:先把用户问题向量化,去向量库里简单一搜,把最相似的文档片段直接塞进大模型,让它“看着答”
优点:实现简单、延迟低;缺点:检索粗、易幻觉、难处理复杂语境。一句话——RAG 的“毛坯房”版

NativeRAG的步骤:

Indexing (数据切分后向量化并存储)=> 如何更好地把知识存起来

Retrieval检索 (找到相似度高的向量)=> 如何在大量的知识中,找到一小部分有用的,给到模型参考

Generation产生 => 如何结合用户的提问和检索到的知识,让模型生成有用的答案

这三个步骤虽然看似简单,但在RAG 应用从构建到落地实施的整个过程中,涉及较多复杂的工作内容。RAG有点像推荐系统,推荐系统不一定是最准确的但是他一定是相关的

三、Embedding模型选择

选择不同的Embedding模型直接影响到我们对文档理解的质量

1、Embedding模型能力排名

https://huggingface.co/spaces/mteb/leaderboard 比较了1000多种语言中的100多种文本嵌入模型

2、有哪些常见的Embedding模型?

分类模型名称特点适用场景文件大小
通用文本嵌入模型BGE-M3(智源研究院)支持100+语言,输入长度达8192 tokens,融合密集、稀疏、多向量混合检索,适合跨语言长文档检索跨语言长文档检索、高精度RAG应用2.3 G
text-embedding-3-large(OpenAI)向量维度3072,长文本语义捕捉能力强,英文表现优秀英文内容优先的全球化应用
Jina-embeddings-v2-small(Jina AI)参数量仅35 M,支持实时推理(RT<50 ms),适合轻量化部署轻量级文本处理、实时推理任务
中文嵌入模型xiaobu-embedding-v2针对中文语义优化,语义理解能力强中文文本分类、语义检索
M3E-Base针对中文优化的轻量模型,适合本地私有化部署中文法律、医疗领域检索任务0.4 G
stella-mrl-large-zh-v3.5-1792处理大规模中文数据能力强,捕捉细微语义关系中文文本高级语义分析、自然语言处理任务
指令(具体任务)驱动与复杂任务模型gte-Qwen2-7B-instruct(阿里巴巴)基于Qwen大模型微调,支持代码与文本跨模态检索复杂指令驱动任务、智能问答系统
E5-mistral-7B(Microsoft)基于Mistral架构,Zero-shot任务表现优异动态调整语义密度的复杂系统
企业级与复杂系统BGE-M3(智源研究院)适合企业级部署,支持混合检索企业级语义检索、复杂RAG应用2.3 G
E5-mistral-7B(Microsoft)适合企业级部署,支持指令微调需要动态调整语义密度的复杂系统

购买GPU云主机,完成初始化和部署:

https://console.compshare.cn/light-gpu/console/resources

https://passport.compshare.cn/register?referral_code=BwJtdGNa5q1FARKqawOE27

  1. 安装 pip(与系统 python3 匹配)

apt update

apt install python3-pip -y

  1. 验证

python3 -m pip --version

应看到类似输出:

pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)

  1. 安装 modelscope

python3 -m pip install modelscope

如需国内镜像加速:

python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple modelscope

python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple FlagEmbedding

完成后再次运行脚本

3、Embedding模型 bge-m3的实际使用

python3 bge-m3.py

bge-m3.py

#!/usr/bin/env python
# coding: utf-8
# In[1]:
# 第一步:下载BGE M3模型并放置到指定目录
# 使用modelscope库的snapshot_download函数下载模型
from modelscope import snapshot_download
# 下载模型到/root/autodl-tmp/models目录下并返回模型路径
model_dir = snapshot_download('BAAI/bge-m3', cache_dir='/root/autodl-tmp/models')
# In[1]:
# 第二步:导入并初始化BGE M3模型,向量维度是1024,不同的模型向量维度不同
from FlagEmbedding import BGEM3FlagModel
# 创建BGEM3FlagModel实例
# 参数1:模型路径
# 参数2:use_fp16=True 使用半精度浮点数计算,加快计算速度,性能略有下降
model = BGEM3FlagModel('/root/autodl-tmp/models/BAAI/bge-m3',  
                       use_fp16=True) 
# 第三步:准备需要编码的句子
# 第一组句子
sentences_1 = ["What is BGE M3?", "Defination of BM25"]
# 第二组句子
sentences_2 = ["BGE M3 is an embedding model supporting dense retrieval, lexical matching and multi-vector interaction.", 
               "BM25 is a bag-of-words retrieval function that ranks a set of documents based on the query terms appearing in each document"]
# 第四步:对句子进行编码得到嵌入向量
# 对第一组句子进行编码
# batch_size=12:批处理大小
# max_length=8192:最大序列长度,不需要这么长时可以设置更小的值来加快编码速度
embeddings_1 = model.encode(sentences_1, 
                            batch_size=12, 
                            max_length=8192, 
                            )['dense_vecs']  # 获取密集向量
# 对第二组句子进行编码(使用默认参数)
embeddings_2 = model.encode(sentences_2)['dense_vecs']
# 第五步:计算相似度矩阵
# 使用矩阵乘法计算两组嵌入向量之间的余弦相似度
similarity = embeddings_1 @ embeddings_2.T
# 打印相似度矩阵
# 结果解释:矩阵中的每个元素[i,j]表示sentences_1[i]和sentences_2[j]之间的相似度
print(similarity)
# 预期输出示例:[[0.6265, 0.3477], [0.3499, 0.678 ]]
# 其中第一行表示"What is BGE M3?"与两个句子的相似度
# 第二行表示"Defination of BM25"与两个句子的相似度

运行成功

下一个案例:

Python 版本 3.11.13

python3 gte-qwen2-1.py

安装依赖:

python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com sentence_transformers

4、Embedding模型 gte-qwen2的实际使用

gte-qwen2-1.py


#!/usr/bin/env python3
# coding: utf-8
# --------------------------------------------------
# 0. 先打补丁(必须在 import transformers 之前)
# --------------------------------------------------
# 导入 transformers 库和 DynamicCache 类
import transformers
from transformers.cache_utils import DynamicCache
# 检查 DynamicCache 类是否缺少 get_usable_length 方法
# 这是为了兼容不同版本的 transformers 库
if not hasattr(DynamicCache, 'get_usable_length'):
    # 定义一个兼容方法,接收 kv_seq_len 和 layer_idx 参数
    # 但实际上只返回 get_seq_length() 的结果
    def get_usable_length(self, kv_seq_len, layer_idx=0):
        return self.get_seq_length()
    # 动态添加方法到 DynamicCache 类
    setattr(DynamicCache, 'get_usable_length', get_usable_length)
# --------------------------------------------------
# 1. 下载模型
# --------------------------------------------------
# 从 modelscope 导入 snapshot_download 函数用于下载模型
from modelscope import snapshot_download
# 下载 gte_Qwen2-1.5B-instruct 模型到指定的缓存目录
# gte (General Text Embedding) 是一种通用文本嵌入模型
# 基于 Qwen2-1.5B 模型架构,适合用于各种文本嵌入任务
model_dir = snapshot_download(
    'iic/gte_Qwen2-1.5B-instruct',  # 模型名称
    cache_dir='/root/autodl-tmp/models'  # 模型缓存目录
)
# --------------------------------------------------
# 2. 加载 tokenizer / model
# --------------------------------------------------
# 导入所需的库和类
from transformers import AutoTokenizer, AutoModel
import torch
import torch.nn.functional as F
# 设置运行设备:优先使用 GPU (cuda),否则使用 CPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# 加载 tokenizer,用于文本处理和编码
# trust_remote_code=True 允许执行模型仓库中的自定义代码
tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
# 设置 padding 方向为左侧,这对某些模型的性能有影响
# 特别是在处理对话或有上下文的文本时
# 如果 tokenizer 没有 pad_token,则使用 eos_token 作为 pad_token
tokenizer.padding_side = 'left'
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token
# 加载模型并移至指定设备,设置为评估模式
# eval() 模式会关闭一些特定于训练的功能,如 dropout
model = AutoModel.from_pretrained(model_dir, trust_remote_code=True).to(device).eval()
# --------------------------------------------------
# 3. 工具函数
# --------------------------------------------------
def last_token_pool(hidden_states: torch.Tensor,
                    attention_mask: torch.Tensor) -> torch.Tensor:
    """
    从模型的最后一层隐藏状态中提取最后一个有效 token 的表示
    参数:
        hidden_states: 模型输出的隐藏状态张量,形状为 [batch_size, seq_len, hidden_dim]
        attention_mask: 注意力掩码张量,形状为 [batch_size, seq_len]
    返回:
        池化后的特征向量,形状为 [batch_size, hidden_dim]
    """
    # 计算每个序列中有效 token 的数量,然后减去 1 得到最后一个有效 token 的位置
    seq_len = attention_mask.sum(dim=1) - 1
    # 获取批次大小
    batch_size = hidden_states.size(0)
    # 从每个序列中提取最后一个有效 token 的表示
    return hidden_states[torch.arange(batch_size, device=hidden_states.device), seq_len]
# --------------------------------------------------
# 4. 数据
# --------------------------------------------------
# 定义任务描述,告诉模型我们要进行的是搜索查询与相关段落的匹配任务
task = 'Given a web search query, retrieve relevant passages that answer the query'
# 准备查询文本,每个查询包含任务指令和具体问题
# 这种格式有助于模型理解任务上下文,提高嵌入质量
queries = [
    f'Instruct: {task}\nQuery: how much protein should a female eat',  # 关于女性蛋白质摄入量的查询
    f'Instruct: {task}\nQuery: summit define',  # 关于 summit 定义的查询
]
# 准备文档文本,这些是可能与查询相关的文本段落
documents = [
    "As a general guideline, the CDC's average requirement of protein for women ages 19 to 70 is 46 grams per day...",
    "Definition of summit for English Language Learners..."
]
# 设置最大序列长度,适用于 gte-Qwen2 模型支持的较长上下文
MAX_LEN = 8192
# --------------------------------------------------
# 5. 逐条编码(避免 KV 长度错位)
# --------------------------------------------------
def embed(texts):
    """
    将文本列表转换为嵌入向量列表
    逐条处理文本以避免 KV 缓存长度不匹配的问题
    参数:
        texts: 文本字符串列表
    返回:
        文本的嵌入向量张量,形状为 [len(texts), embedding_dim]
    """
    vecs = []
    # 逐条处理文本,避免批处理可能导致的 KV 缓存问题
    for txt in texts:
        # 使用 tokenizer 处理文本
        inputs = tokenizer(
            txt,
            return_tensors='pt',  # 返回 PyTorch 张量
            max_length=MAX_LEN,   # 最大长度限制
            truncation=True,      # 超出长度时截断
            padding=True          # 不足长度时填充
        ).to(device)  # 移至指定设备
        # 关闭梯度计算以提高效率并节省内存
        with torch.no_grad():
            # 获取模型输出
            outputs = model(**inputs)
            # 提取最后一个有效 token 的表示
            vec = last_token_pool(outputs.last_hidden_state, inputs['attention_mask'])
            # 对向量进行 L2 归一化,使所有向量具有相同的模长
            vecs.append(F.normalize(vec, p=2, dim=1))
    # 将所有向量在批次维度上拼接
    return torch.cat(vecs, dim=0)
# 获取查询和文档的嵌入向量
q_emb = embed(queries)
d_emb = embed(documents)
# --------------------------------------------------
# 6. 相似度 & 输出
# --------------------------------------------------
# 计算查询和文档之间的余弦相似度
# q_emb @ d_emb.T 计算所有查询与所有文档之间的点积
# 由于向量已经归一化,点积等于余弦相似度
# 乘以 100 是为了使结果更直观(范围从 -100 到 100)
scores = (q_emb @ d_emb.T) * 100
# 打印相似度分数
# 每行代表一个查询与所有文档的相似度
# 每列代表一个文档与所有查询的相似度
print(scores.tolist())

运行成功:

gte-qwen2-2.py


#!/usr/bin/env python3
# coding: utf-8
# --------------------------------------------------
# 0. 先打补丁(必须在 import transformers 之前)
# --------------------------------------------------
# 导入 transformers 库和 DynamicCache 类
import transformers
from transformers.cache_utils import DynamicCache
# 检查 DynamicCache 类是否缺少 get_usable_length 方法
# 这是为了兼容不同版本的 transformers 库
if not hasattr(DynamicCache, 'get_usable_length'):
    # 定义一个兼容方法,接收 kv_seq_len 和 layer_idx 参数
    # 但实际上只返回 get_seq_length() 的结果
    def get_usable_length(self, kv_seq_len, layer_idx=0):
        return self.get_seq_length()
    # 动态添加方法到 DynamicCache 类
    setattr(DynamicCache, 'get_usable_length', get_usable_length)
# --------------------------------------------------
# 1. 依赖
# --------------------------------------------------
# 导入 PyTorch 深度学习库
import torch
# 导入 PyTorch 函数式接口,包含各种神经网络函数
import torch.nn.functional as F
# 导入 Tensor 类型,用于类型提示
from torch import Tensor
# 从 transformers 导入自动分词器和模型加载器
from transformers import AutoTokenizer, AutoModel
# --------------------------------------------------
# 2. 工具函数
# --------------------------------------------------
def last_token_pool(hidden_states: torch.Tensor,
                    attention_mask: torch.Tensor) -> torch.Tensor:
    """
    从模型的最后一层隐藏状态中提取最后一个有效 token 的表示
    参数:
        hidden_states: 模型输出的隐藏状态张量,形状为 [batch_size, seq_len, hidden_dim]
        attention_mask: 注意力掩码张量,形状为 [batch_size, seq_len]
    返回:
        池化后的特征向量,形状为 [batch_size, hidden_dim]
    """
    # 计算每个序列中有效 token 的数量,然后减去 1 得到最后一个有效 token 的位置
    seq_len = attention_mask.sum(dim=1) - 1
    # 获取批次大小
    batch_size = hidden_states.size(0)
    # 从每个序列中提取最后一个有效 token 的表示
    return hidden_states[torch.arange(batch_size, device=hidden_states.device), seq_len]
def get_detailed_instruct(task_description: str, query: str) -> str:
    """
    将任务描述和查询组合成模型所需的指令格式
    参数:
        task_description: 任务的详细描述
        query: 具体的查询内容
    返回:
        格式化后的指令字符串
    """
    return f'Instruct: {task_description}\nQuery: {query}'
# --------------------------------------------------
# 3. 数据
# --------------------------------------------------
# 定义任务描述,告诉模型我们要进行的是搜索查询与相关段落的匹配任务
task = 'Given a web search query, retrieve relevant passages that answer the query'
# 准备查询文本,使用 get_detailed_instruct 函数将任务描述和查询组合成特定格式
queries = [
    get_detailed_instruct(task, 'how much protein should a female eat'),  # 关于女性蛋白质摄入量的查询
    get_detailed_instruct(task, 'summit define'),  # 关于 summit 定义的查询
]
# 准备文档文本,这些是可能与查询相关的文本段落
documents = [
    "As a general guideline, the CDC's average requirement of protein for women ages 19 to 70 is 46 grams per day...",
    "Definition of summit for English Language Learners..."
]
# --------------------------------------------------
# 4. 加载 tokenizer / model
# --------------------------------------------------
# 模型目录路径,指向本地已下载的 gte_Qwen2-1.5B-instruct 模型
model_dir = "/root/autodl-tmp/models/iic/gte_Qwen2-1___5B-instruct"
# 加载 tokenizer,用于文本处理和编码
# trust_remote_code=True 允许执行模型仓库中的自定义代码
tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
# 设置 padding 方向为左侧,这对某些模型的性能有影响
# 特别是在处理对话或有上下文的文本时
tokenizer.padding_side = 'left'
# 如果 tokenizer 没有 pad_token,则使用 eos_token 作为 pad_token
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token
# 设置运行设备:优先使用 GPU (cuda),否则使用 CPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# 加载模型并移至指定设备,设置为评估模式
# eval() 模式会关闭一些特定于训练的功能,如 dropout
model = AutoModel.from_pretrained(model_dir, trust_remote_code=True).to(device).eval()
# 设置最大序列长度,适用于 gte-Qwen2 模型支持的较长上下文
MAX_LEN = 8192
# --------------------------------------------------
# 5. 逐条编码(避免 KV 长度错位)
# --------------------------------------------------
def embed(texts):
    """
    将文本列表转换为嵌入向量列表
    逐条处理文本以避免 KV 缓存长度不匹配的问题
    参数:
        texts: 文本字符串列表
    返回:
        文本的嵌入向量张量,形状为 [len(texts), embedding_dim]
    """
    vecs = []
    # 逐条处理文本,避免批处理可能导致的 KV 缓存问题
    for txt in texts:
        # 使用 tokenizer 处理文本
        inputs = tokenizer(
            txt,
            return_tensors='pt',  # 返回 PyTorch 张量
            max_length=MAX_LEN,   # 最大长度限制
            truncation=True,      # 超出长度时截断
            padding=True          # 不足长度时填充
        ).to(device)  # 移至指定设备
        # 关闭梯度计算以提高效率并节省内存
        with torch.no_grad():
            # 获取模型输出
            outputs = model(**inputs)
            # 提取最后一个有效 token 的表示
            vec = last_token_pool(outputs.last_hidden_state, inputs['attention_mask'])
            # 对向量进行 L2 归一化,使所有向量具有相同的模长
            vecs.append(F.normalize(vec, p=2, dim=1))
    # 将所有向量在批次维度上拼接
    return torch.cat(vecs, dim=0)
# 获取查询和文档的嵌入向量
q_emb = embed(queries)
d_emb = embed(documents)
# --------------------------------------------------
# 6. 相似度 & 输出
# --------------------------------------------------
# 计算查询和文档之间的余弦相似度
# q_emb @ d_emb.T 计算所有查询与所有文档之间的点积
# 由于向量已经归一化,点积等于余弦相似度
# 乘以 100 是为了使结果更直观(范围从 -100 到 100)
scores = (q_emb @ d_emb.T) * 100
# 打印相似度分数
# 每行代表一个查询与所有文档的相似度
# 每列代表一个文档与所有查询的相似度
# 预期结果应该显示每个查询与对应的相关文档有较高的相似度
print(scores.tolist())
# 结果解释:
# [[70.00666809082031, 8.184867858886719], [14.62420654296875, 77.71405792236328]]
# 70.00: 第一个查询(蛋白质摄入)与第一个文档(蛋白质指南)的相似度(高相关)
# 8.18: 第一个查询(蛋白质摄入)与第二个文档(summit定义)的相似度(低相关)
# 14.62: 第二个查询(summit定义)与第一个文档(蛋白质指南)的相似度(低相关)
# 77.71: 第二个查询(summit定义)与第二个文档(summit定义)的相似度(高相关)

运行成功:

5、Embedding模型使用总结

项目内容
模型名称gte-Qwen2-7B-instruct
模型类型基于 Qwen2 的指令优化型嵌入模型
指令优化在大规模指令-响应对上精细训练,擅长解析复杂指令并给出准确、自然的回答
性能表现文本生成、问答、分类、情感分析、命名实体识别、语义匹配等任务均表现优异
适用场景复杂问答系统、多步推理、跨语言 NLP 任务,需要高语义理解能力的应用
核心优势- 指令理解与执行能力突出,支持复杂指令驱动 - 多语言覆盖,跨语种表现一致 - 在文本生成与语义理解方面领先
局限模型体量大,对 GPU/内存资源要求高,推荐在资源充足的服务器或云端部署

四、DeepSeek + Faiss 搭建本地知识库检索

1、 项目的架构与技术选型是什么?

(1)RAG架构

检索(Retrieval):使用向量相似度搜索从PDF文档中检索相关内容

增强(Augmentation):将检索到的文档片段作为上下文

生成(Generation):基于上下文和用户问题生成答案

(2)技术栈选择

• 向量数据库:Faiss作为 高效的向量检索

• 嵌入模型:阿里云DashScope的text-embedding-v1

• 大语言模型:deepseek-v3

• 文档处理:PyPDF2用于PDF文本提取

2、程序的逻辑结构是什么?

Step1:文档预处理

PDF文件 → 文本提取 → 文本分割 → 页码映射

1) PDF文本提取

• 逐页提取文本内容

• 记录每行文本对应的页码信息

• 处理空页和异常情况

2) 文本分割策略

• 使用递归字符分割器

• 分割参数:chunk_size=1000, chunk_overlap=200

• 分割符优先级:段落 → 句子 → 空格 → 字符

3) 页码映射处理

• 基于字符位置计算每个文本块的页码

• 使用众数统计确定文本块的主要来源页码

• 建立文本块与页码的映射关系

Step2:知识库构建

文本块→ 嵌入向量 → Faiss索引 → 本地持久化

1) 向量数据库构建

• 使用DashScope嵌入模型生成向量

• 将向量存储到Faiss索引结构

2) 数据持久化

• 保存Faiss索引文件(.faiss)

• 保存元数据信息(.pkl)

• 保存页码映射关系(page_info.pkl)

Step3:问答查询

用户问题→ 向量检索 → 文档组合 → LLM生成 → 答案输出

1) 相似度检索

• 将用户问题转换为向量

• 在Faiss中搜索最相似的文档块,返回Top-K相关文档

2) 问答链处理

• 使用LangChain的load_qa_chain

• 采用 stuff 策略组合文档

• 将组合后的上下文和问题发送给LLM

3)程序运行结果展示

chatpdf-faiss.py


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
PDF文档问答系统
该脚本实现了一个基于FAISS向量数据库和大语言模型的PDF文档问答系统,
可以从PDF中提取文本,创建向量索引,并基于相似度搜索进行问答。
"""
# 导入必要的库
from PyPDF2 import PdfReader  # 用于读取PDF文件
from langchain.chains.question_answering import load_qa_chain  # 加载问答链
from langchain.text_splitter import RecursiveCharacterTextSplitter  # 文本分割工具
from langchain_community.embeddings import DashScopeEmbeddings  # 阿里云DashScope嵌入模型
from langchain_community.vectorstores import FAISS  # FAISS向量数据库
from typing import List, Tuple  # 类型提示
import os  # 操作系统接口
import pickle  # 序列化工具
# 从环境变量获取DashScope API密钥
DASHSCOPE_API_KEY = os.getenv('DASHSCOPE_API_KEY')
if not DASHSCOPE_API_KEY:
    raise ValueError("请设置环境变量 DASHSCOPE_API_KEY")
def extract_text_with_page_numbers(pdf) -> Tuple[str, List[Tuple[str, int]]]:
    """
    从PDF中提取文本并记录每个字符对应的页码
    参数:
        pdf: PDF文件对象,通过PdfReader创建
    返回:
        Tuple[str, List[int]]: 包含两个元素的元组
            - 提取的完整文本内容
            - 每个字符对应的页码列表
    """
    text = ""  # 存储提取的文本
    char_page_mapping = []  # 存储每个字符对应的页码
    # 遍历PDF的每一页
    for page_number, page in enumerate(pdf.pages, start=1):
        # 提取当前页的文本
        extracted_text = page.extract_text()
        if extracted_text:
            text += extracted_text
            # 为当前页面的每个字符记录页码
            char_page_mapping.extend([page_number] * len(extracted_text))
        else:
            print(f"第 {page_number} 页未找到文本。")
    return text, char_page_mapping
def process_text_with_splitter(text: str, char_page_mapping: List[int], save_path: str = None) -> FAISS:
    """
    处理文本并创建向量存储
    参数:
        text: 提取的文本内容
        char_page_mapping: 每个字符对应的页码列表
        save_path: 可选,保存向量数据库的路径
    返回:
        FAISS: 基于FAISS的向量存储对象,包含文本嵌入和检索功能
    """
    # 创建文本分割器,用于将长文本分割成小块
    text_splitter = RecursiveCharacterTextSplitter(
        separators=["\n\n", "\n", ".", " ", ""],  # 分割符优先级
        chunk_size=1000,  # 每个文本块的大小
        chunk_overlap=200,  # 文本块之间的重叠部分大小
        length_function=len,  # 计算长度的函数
    )
    # 分割文本为多个文本块
    chunks = text_splitter.split_text(text)
    print(f"文本被分割成 {len(chunks)} 个块。")
    # 创建DashScope嵌入模型,用于将文本转换为向量
    embeddings = DashScopeEmbeddings(
        model="text-embedding-v1",  # 使用的嵌入模型
        dashscope_api_key=DASHSCOPE_API_KEY,  # API密钥
    )
    # 从文本块创建FAISS向量知识库
    knowledgeBase = FAISS.from_texts(chunks, embeddings)
    print("已从文本块创建知识库。")
    # 为每个文本块找到对应的页码信息
    page_info = {}
    current_pos = 0  # 当前处理位置
    for chunk in chunks:
        chunk_start = current_pos
        chunk_end = current_pos + len(chunk)
        # 找到这个文本块中字符对应的所有页码
        chunk_pages = char_page_mapping[chunk_start:chunk_end]
        # 取页码的众数(出现最多的页码)作为该块的页码
        if chunk_pages:
            # 统计每个页码出现的次数
            page_counts = {}
            for page in chunk_pages:
                page_counts[page] = page_counts.get(page, 0) + 1
            # 找到出现次数最多的页码
            most_common_page = max(page_counts, key=page_counts.get)
            page_info[chunk] = most_common_page
        else:
            page_info[chunk] = 1  # 默认页码
        current_pos = chunk_end
    # 将页码信息存储在知识库对象中
    knowledgeBase.page_info = page_info
    print(f'页码映射完成,共 {len(page_info)} 个文本块')
    # 如果提供了保存路径,则保存向量数据库和页码信息
    if save_path:
        # 确保目录存在
        os.makedirs(save_path, exist_ok=True)
        # 保存FAISS向量数据库
        knowledgeBase.save_local(save_path)
        print(f"向量数据库已保存到: {save_path}")
        # 保存页码信息到同一目录
        with open(os.path.join(save_path, "page_info.pkl"), "wb") as f:
            pickle.dump(page_info, f)
        print(f"页码信息已保存到: {os.path.join(save_path, 'page_info.pkl')}")
    return knowledgeBase
def load_knowledge_base(load_path: str, embeddings = None) -> FAISS:
    """
    从磁盘加载向量数据库和页码信息
    参数:
        load_path: 向量数据库的保存路径
        embeddings: 可选,嵌入模型。如果为None,将创建一个新的DashScopeEmbeddings实例
    返回:
        FAISS: 加载的FAISS向量数据库对象
    """
    # 如果没有提供嵌入模型,则创建一个新的
    if embeddings is None:
        embeddings = DashScopeEmbeddings(
            model="text-embedding-v1",
             dashscope_api_key=DASHSCOPE_API_KEY,
        )
    # 加载FAISS向量数据库,添加allow_dangerous_deserialization=True参数以允许反序列化
    knowledgeBase = FAISS.load_local(load_path, embeddings, allow_dangerous_deserialization=True)
    print(f"向量数据库已从 {load_path} 加载。")
    # 加载页码信息
    page_info_path = os.path.join(load_path, "page_info.pkl")
    if os.path.exists(page_info_path):
        with open(page_info_path, "rb") as f:
            page_info = pickle.load(f)
        knowledgeBase.page_info = page_info
        print("页码信息已加载。")
    else:
        print("警告: 未找到页码信息文件。")
    return knowledgeBase
# 主程序开始
# 读取PDF文件
pdf_reader = PdfReader('./浦发上海浦东发展银行西安分行个金客户经理考核办法.pdf')
# 提取文本和页码信息
text, char_page_mapping = extract_text_with_page_numbers(pdf_reader)
# 输出提取的文本长度信息
print(f"提取的文本长度: {len(text)} 个字符。")
# 处理文本并创建知识库,同时保存到磁盘
save_dir = "./vector_db"
knowledgeBase = process_text_with_splitter(text, char_page_mapping, save_path=save_dir)
# 示例:如何加载已保存的向量数据库
# 注释掉以下代码以避免在当前运行中重复加载
"""
# 创建嵌入模型
embeddings = DashScopeEmbeddings(
    model="text-embedding-v1",
    dashscope_api_key=DASHSCOPE_API_KEY,
)
# 从磁盘加载向量数据库
loaded_knowledgeBase = load_knowledge_base("./vector_db", embeddings)
# 使用加载的知识库进行查询
docs = loaded_knowledgeBase.similarity_search("客户经理每年评聘申报时间是怎样的?")
# 直接使用FAISS.load_local方法加载(替代方法)
# loaded_knowledgeBase = FAISS.load_local("./vector_db", embeddings, allow_dangerous_deserialization=True)
# 注意:使用这种方法加载时,需要手动加载页码信息
"""
# 创建大语言模型实例,用于生成回答
from langchain_community.llms import Tongyi
llm = Tongyi(model_name="qwen-turbo", dashscope_api_key=DASHSCOPE_API_KEY) # 使用qwen-turbo模型
# 设置查询问题
query = "客户经理被投诉了,投诉一次扣多少分"
query = "客户经理每年评聘申报时间是怎样的?"
if query:
    # 执行相似度搜索,找到与查询相关的文档
    docs = knowledgeBase.similarity_search(query,k=10)  # k=10表示返回最相关的10个文档
    # 加载问答链,用于结合相关文档和问题生成回答
    chain = load_qa_chain(llm, chain_type="stuff")  # stuff模式表示将所有文档合并到上下文中
    # 准备输入数据
    input_data = {"input_documents": docs, "question": query}
    # 执行问答链,生成回答
    response = chain.invoke(input=input_data)
    print(response["output_text"])  # 输出回答内容
    print("来源:")
    # 记录唯一的页码,避免重复显示
    unique_pages = set()
    # 显示每个文档块的来源页码
    for doc in docs:
        text_content = getattr(doc, "page_content", "")  # 获取文档内容
        # 从页码映射中获取该内容对应的页码
        source_page = knowledgeBase.page_info.get(
            text_content.strip(), "未知"
        )
        # 只显示唯一的页码
        if source_page not in unique_pages:
            unique_pages.add(source_page)
            print(f"文本块页码: {source_page}")

运行成功

在这里插入图片描述

3、总结

(1)PDF文本提取与处理

使用PyPDF2库的PdfReader从PDF文件中提取文本在提取过程中记录每行文本对应的页码,便于后续溯源使用RecursiveCharacterTextSplitter将长文本分割成小块,便于向量化处理

(2)向量数据库构建

使用DashScopeEmbeddings将文本块转换为向量表示使用FAISS向量数据库存储文本向量,支持高效的相似度搜索为每个文本块保存对应的页码信息,实现查询结果溯源

(3)语义搜索与问答链

基于用户查询,使用similarity_search在向量数据库中检索相关文本块使用Qwen语言模型和load_qa_chain构建问答链将检索到的文档和用户问题作为输入,生成回答

(4)成本跟踪与结果展示

如果是openai模型,可以用get_openai_callback跟踪API调用成本展示问答结果和来源页码,方便用户验证信息

4、LangChain中的问答链

LangChain问答链中的4种chain_type:

项目chain_type一句话定义处理流程示意最适用场景主要优点主要缺点
1stuff(最暴力,也最简单)一次把所有文档塞进 prompt[全部文档] → LLM → 答案文档很短,远小于模型 token 上限代码最少、调用最快、成本最低容易超 token;长文噪声大
2map_reduce(先分治,再汇总)先对每个 chunk 单独问,再把所有中间结果合并[chunk₁→LLM] … [chunkₙ→LLM] → 汇总 LLM → 答案长文档摘要 / 全局问答可并行、支持超长文本至少 2 次 LLM 调用;可能丢失跨 chunk 语境
3refine(滚动精修)滚动式精修:用前一个答案 + 下一个 chunk 继续优化chunk₁→LLM 得 ans₁ → (ans₁+chunk₂)→LLM 得 ans₂ … → 最终答案需要逐步吸收信息,后文可能修正前文答案连贯,token 利用率较高串行调用,延迟随 chunk 数量线性增加
4map_rerank(打分挑最佳)对每个 chunk 让 LLM 打分,只返回得分最高 chunk 的答案[chunk→LLM+score] × n → 取最高 score 的答案只需“最相关”单段即可回答的高精度场景只返回最相关片段,token 最省可能遗漏跨 chunk 信息;打分质量决定效果

(1) stuff(比较常用的策略)

适合文档拆分的比较小,一次获取文档比较少的情况调用LLM 的次数也比较少,能使用 stuff 的就使用这种方式

(2) map_reduce

将每个document 单独处理,可以并发进行调用。但是每个文档之间缺少上下文

(3) refine

Refine 这种方式能部分保留上下文,以及token 的使用能控制在一定范围

(4) map_rerank

会大量地调用LLM,每个document 之间是独立处理

5、 如果LLM可以处理无限上下文了,RAG还有意义吗?

效率与成本:LLM处理长上下文时计算资源消耗大,响应时间增加。RAG通过检索相关片段,减少输入长度

知识更新:LLM的知识截止于训练数据,无法实时更新。RAG可以连接外部知识库,增强时效性

可解释性:RAG的检索过程透明,用户可查看来源,增强信任。LLM的生成过程则较难追溯

定制化:RAG可针对特定领域定制检索系统,提供更精准的结果,而LLM的通用性可能无法满足特定需求

数据隐私:RAG允许在本地或私有数据源上检索,避免敏感数据上传云端,适合隐私要求高的场景

===> 结合LLM的生成能力和RAG的检索能力,可以提升整体性能,提供更全面、准确的回答

普通人如何抓住AI大模型的风口?

领取方式在文末

为什么要学习大模型?

目前AI大模型的技术岗位与能力培养随着人工智能技术的迅速发展和应用 , 大模型作为其中的重要组成部分 , 正逐渐成为推动人工智能发展的重要引擎 。大模型以其强大的数据处理和模式识别能力, 广泛应用于自然语言处理 、计算机视觉 、 智能推荐等领域 ,为各行各业带来了革命性的改变和机遇 。

目前,开源人工智能大模型已应用于医疗、政务、法律、汽车、娱乐、金融、互联网、教育、制造业、企业服务等多个场景,其中,应用于金融、企业服务、制造业和法律领域的大模型在本次调研中占比超过 30%。
在这里插入图片描述

随着AI大模型技术的迅速发展,相关岗位的需求也日益增加。大模型产业链催生了一批高薪新职业:
在这里插入图片描述

人工智能大潮已来,不加入就可能被淘汰。如果你是技术人,尤其是互联网从业者,现在就开始学习AI大模型技术,真的是给你的人生一个重要建议!

最后

只要你真心想学习AI大模型技术,这份精心整理的学习资料我愿意无偿分享给你,但是想学技术去乱搞的人别来找我!

在当前这个人工智能高速发展的时代,AI大模型正在深刻改变各行各业。我国对高水平AI人才的需求也日益增长,真正懂技术、能落地的人才依旧紧缺。我也希望通过这份资料,能够帮助更多有志于AI领域的朋友入门并深入学习。

真诚无偿分享!!!
vx扫描下方二维码即可
加上后会一个个给大家发

在这里插入图片描述

大模型全套学习资料展示

自我们与MoPaaS魔泊云合作以来,我们不断打磨课程体系与技术内容,在细节上精益求精,同时在技术层面也新增了许多前沿且实用的内容,力求为大家带来更系统、更实战、更落地的大模型学习体验。

图片

希望这份系统、实用的大模型学习路径,能够帮助你从零入门,进阶到实战,真正掌握AI时代的核心技能!

01 教学内容

图片

  • 从零到精通完整闭环:【基础理论 →RAG开发 → Agent设计 → 模型微调与私有化部署调→热门技术】5大模块,内容比传统教材更贴近企业实战!

  • 大量真实项目案例: 带你亲自上手搞数据清洗、模型调优这些硬核操作,把课本知识变成真本事‌!

02适学人群

应届毕业生‌: 无工作经验但想要系统学习AI大模型技术,期待通过实战项目掌握核心技术。

零基础转型‌: 非技术背景但关注AI应用场景,计划通过低代码工具实现“AI+行业”跨界‌。

业务赋能突破瓶颈: 传统开发者(Java/前端等)学习Transformer架构与LangChain框架,向AI全栈工程师转型‌。

image.png

vx扫描下方二维码即可
在这里插入图片描述

本教程比较珍贵,仅限大家自行学习,不要传播!更严禁商用!

03 入门到进阶学习路线图

大模型学习路线图,整体分为5个大的阶段:
图片

04 视频和书籍PDF合集

图片

从0到掌握主流大模型技术视频教程(涵盖模型训练、微调、RAG、LangChain、Agent开发等实战方向)

图片

新手必备的大模型学习PDF书单来了!全是硬核知识,帮你少走弯路(不吹牛,真有用)
图片

05 行业报告+白皮书合集

收集70+报告与白皮书,了解行业最新动态!
图片

06 90+份面试题/经验

AI大模型岗位面试经验总结(谁学技术不是为了赚$呢,找个好的岗位很重要)图片
在这里插入图片描述

07 deepseek部署包+技巧大全

在这里插入图片描述

由于篇幅有限

只展示部分资料

并且还在持续更新中…

真诚无偿分享!!!
vx扫描下方二维码即可
加上后会一个个给大家发

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值