【AI大模型开发】-创建RAG问答实战(LangChain+DeepSeek+Faiss)

1. 项目简介

ChatPDF-Faiss 是一个基于 FAISS 向量数据库的 PDF 文档智能问答系统,能够从 PDF 文档中提取信息并回答用户的问题。该系统利用了阿里云 DashScope API 提供的文本嵌入和大语言模型能力,实现了对 PDF 文档的高效检索和智能问答。

1.1 核心功能

  • 从 PDF 文件中提取文本内容和页码信息
  • 使用递归字符分割器将文本分割成小块
  • 使用 DashScope Embeddings 生成文本嵌入
  • 使用 FAISS 创建和管理向量数据库
  • 支持将向量数据库保存到磁盘并重新加载
  • 使用 Tongyi LLM 生成智能回答
  • 显示回答的来源页码,提高可追溯性

2. 技术架构

本项目采用了以下技术栈:

2.1 主要依赖

  • PyPDF2:用于从 PDF 文件中提取文本
  • langchain_community:提供向量存储和 LLM 接口
  • langchain_text_splitters:用于文本分割
  • FAISS:高效的向量相似度搜索库
  • 阿里云 DashScope API:提供文本嵌入和大语言模型服务

2.2 工作流程

  1. 使用 PyPDF2 从 PDF 文件中提取文本和页码信息
  2. 使用 RecursiveCharacterTextSplitter 将文本分割成小块
  3. 使用 DashScopeEmbeddings 生成文本嵌入
  4. 使用 FAISS 创建向量数据库并保存到磁盘
  5. 用户输入查询问题
  6. 系统在向量数据库中进行相似度搜索,找到相关文本块
  7. 将相关文本块作为上下文,使用 Tongyi LLM 生成回答
  8. 显示回答和来源页码

3. 安装与配置

步骤 1:克隆项目

将项目克隆到本地目录:

复制代码
git clone <项目地址>

步骤 2:安装依赖

进入项目目录并安装所需依赖:

复制代码
cd ChatPDF-Faiss
pip install -r requirements.txt

步骤 3:配置 API 密钥

本项目需要使用阿里云 DashScope API,因此需要设置环境变量 DASHSCOPE_API_KEY

Windows 系统:
复制代码
setx DASHSCOPE_API_KEY "你的API密钥"
Linux/Mac 系统:
复制代码
export DASHSCOPE_API_KEY="你的API密钥"

**注意:**设置环境变量后,需要重启终端或 IDE 才能生效。

4. 使用方法

4.1 基本使用

步骤 1:准备 PDF 文件

将需要处理的 PDF 文件放入项目目录,例如 浦发上海浦东发展银行西安分行个金客户经理考核办法.pdf

步骤 2:运行脚本

执行 chatpdf-faiss.py 脚本:

复制代码
python chatpdf-faiss.py

脚本会自动执行以下操作:

  • 从 PDF 文件中提取文本和页码信息
  • 将文本分割成小块并生成嵌入
  • 创建 FAISS 向量数据库并保存到 ./vector_db 目录
  • 使用示例查询测试系统功能

4.2 自定义查询

要使用自定义查询,您可以修改 chatpdf-faiss.py 文件中的 query 变量:

复制代码
# 设置查询问题
query = "客户经理被投诉了,投诉一次扣多少分"
#query = "客户经理每年评聘申报时间是怎样的?"

4.3 加载已保存的向量数据库

如果您已经创建了向量数据库,可以使用 load_knowledge_base 函数加载它,而不需要重新处理 PDF 文件:

复制代码
# 创建嵌入模型
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("你的问题")

5. 代码解析

5.1 核心函数

5.1.1 extract_text_with_page_numbers

从 PDF 文件中提取文本并记录每个字符对应的页码:

复制代码
def extract_text_with_page_numbers(pdf) -> Tuple[str, List[Tuple[str, int]]]:
    """
    从PDF中提取文本并记录每个字符对应的页码
    
    参数:
        pdf: PDF文件对象
    
    返回:
        text: 提取的文本内容
        char_page_mapping: 每个字符对应的页码列表
    """
    text = ""
    char_page_mapping = []
​
    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"No text found on page {page_number}.")
​
    return text, char_page_mapping
5.1.2 process_text_with_splitter

处理文本并创建向量存储:

复制代码
def process_text_with_splitter(text: str, char_page_mapping: List[int], save_path: str = None) -> FAISS:
    """
    处理文本并创建向量存储
    
    参数:
        text: 提取的文本内容
        char_page_mapping: 每个字符对应的页码列表
        save_path: 可选,保存向量数据库的路径
    
    返回:
        knowledgeBase: 基于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)} 个块。")
        
    # 创建嵌入模型
    embeddings = DashScopeEmbeddings(
        model="text-embedding-v1",
        dashscope_api_key=DASHSCOPE_API_KEY,
    )
    
    # 从文本块创建知识库
    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
5.1.3 load_knowledge_base

从磁盘加载向量数据库和页码信息:

复制代码
def load_knowledge_base(load_path: str, embeddings = None) -> FAISS:
    """
    从磁盘加载向量数据库和页码信息
    
    参数:
        load_path: 向量数据库的保存路径
        embeddings: 可选,嵌入模型。如果为None,将创建一个新的DashScopeEmbeddings实例
    
    返回:
        knowledgeBase: 加载的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

6. 示例与应用

6.1 示例查询

以下是一些示例查询及其可能的应用场景:

示例 1:政策查询

查询:"客户经理每年评聘申报时间是怎样的?"

应用场景:人力资源部门了解员工评聘流程

示例 2:处罚规定查询

查询:"客户经理被投诉了,投诉一次扣多少分"

应用场景:员工了解违规处罚规定

示例 3:考核标准查询

查询:"客户经理的考核标准有哪些?"

应用场景:新员工了解工作要求

6.2 实际应用场景

  • 企业文档管理:快速检索和问答企业政策、规章制度等文档
  • 学术研究:从大量学术论文中提取信息并回答问题
  • 法律文件分析:快速了解法律条文和案例
  • 医疗资料查询:从医疗文档中提取关键信息
  • 教育辅助:帮助学生从教材中获取知识

7. 常见问题与解决方案

7.1 常见问题

问题 1:运行脚本时提示缺少 DASHSCOPE_API_KEY 环境变量

解决方案:请按照第 3.3 节的说明设置 DASHSCOPE_API_KEY 环境变量。

问题 2:PDF 文件提取文本失败

解决方案:确保 PDF 文件不是扫描件或图片格式,这些文件需要 OCR 处理才能提取文本。

问题 3:向量数据库加载失败

解决方案 :确保 ./vector_db 目录存在且包含正确的索引文件,同时确保嵌入模型配置正确。

问题 4:回答质量不佳

解决方案:尝试调整文本分割参数,增加搜索的相关文档数量,或使用更高级的 LLM 模型。

7.2 性能优化建议

  • 对于大型 PDF 文件,可以调整 chunk_sizechunk_overlap 参数以平衡检索精度和速度
  • 考虑使用更高级的文本分割策略,如基于段落或章节的分割
  • 对于频繁查询的场景,可以考虑将向量数据库加载到内存中以提高响应速度
  • 如果 API 调用受限,可以考虑使用本地嵌入模型

8. 总结与展望

8.1 项目总结

ChatPDF-Faiss 项目成功实现了一个基于向量数据库的 PDF 文档智能问答系统,具有以下特点:

  • 高效的文本提取和处理能力
  • 准确的向量相似度搜索
  • 智能的问答生成
  • 可追溯的来源页码
  • 易于使用和扩展

8.2 未来展望

未来可以考虑以下功能扩展:

  • 支持多语言 PDF 文档
  • 集成 OCR 功能,支持扫描件 PDF
  • 添加用户界面,提高用户体验
  • 支持批量处理多个 PDF 文件
  • 实现文档自动更新和增量索引
  • 添加对话历史功能,支持多轮对话
  • 集成更多 LLM 模型,提供模型选择功能
相关推荐
qyresearch_4 小时前
全球电子发票市场深度解析:技术驱动、政策引领与绿色转型下的增长新范式
人工智能
反向跟单策略4 小时前
如何正确看待期货反向跟单策略?
大数据·人工智能·学习·数据分析·区块链
东方轧线4 小时前
突破锁竞争的性能枷锁:深度剖析 C++ 内存模型与无锁编程在超大规模并行 AI 系统中的极致应用实践
java·c++·人工智能
AI科技星4 小时前
光的几何起源:从螺旋时空到量子现象的完全统一
开发语言·人工智能·线性代数·算法·机器学习
小程故事多_804 小时前
打破传统桎梏,LLM 让智能运维实现从 “自动化” 到 “自进化”
运维·人工智能·自动化·aigc
星爷AG I4 小时前
9-9 数量与密度(AGI基础理论)
人工智能·agi
Tiaoxiaobai4 小时前
如何实现亚细胞定位
人工智能·笔记
cc_beolus4 小时前
昇腾AI入门
人工智能
AI即插即用4 小时前
即插即用系列 | CVPR 2025 SegMAN: Mamba与局部注意力强强联合,多尺度上下文注意力的新SOTA
图像处理·人工智能·深度学习·目标检测·计算机视觉·视觉检测