RAG初步实战:从 PDF 到问答:我的第一个轻量级 RAG 系统(附详细项目代码内容与说明)

RAG初步实战:从 PDF 到问答:我的第一个轻量级 RAG 系统

项目背景与目标

在大模型逐渐普及的今天,Retrieval-Augmented Generation(RAG,检索增强生成)作为连接“知识库”和“大语言模型”的核心范式,为我们提供了一个高效、实用的路径。为了快速学习RAG的原理,并掌握它的使用方法,我在这开发了一个pdf问答项目

这个项目的初衷,就是以“本地知识问答系统的动手实践”为目标,系统学习并串联以下几个关键知识模块:

✅ 学习目标

模块学习重点
文档解析如何从 PDF 文档中提取结构化文本,并保留元数据(如页码)
文本向量化使用中文 embedding 模型,将自然语言转为向量表征
向量存储与检索搭建本地 FAISS 向量数据库,掌握向量的存储、检索与匹配机制
前端交互使用 Streamlit 构建简单前端,实现交互式问答体验

📌 项目特色
无需翻墙、全本地运行:选用了国内可用的向量化模型和 API 服务,便于部署。

完整链路闭环:从 PDF → Chunk → 向量化 → 检索 → 语言模型生成,一步不落。

结构清晰、易于拓展:代码结构模块化,方便后续更换模型、接入多个文档等。

🧩 项目适合人群
想学习 RAG 工作流程的开发者或学生

需要本地构建问答系统但不方便翻墙的用户

想搭建个人知识库搜索问答助手的 AI 学习者

技术架构概览

   ↓ 文本切分
📜 Chunk 文本 + 元信息↓ 向量化 Embedding(中文模型)
🔍 FAISS 向量数据库↓ 向量相似度匹配(Top-K)
🔁 提取匹配段落(内容 + 元数据)↓ 拼接上下文 Prompt
🧠 百炼智能体 API(对话生成)↓
🧾 用户界面展示(Streamlit)

模块拆解与组件说明

模块使用组件功能说明
文档解析PyMuPDF(或 fitz从 PDF 中按页读取文本,并保留页码信息等元数据
文本切分LangChain将每页文本按段落或长度切分为小块,提高语义粒度
向量化bge-small-zh 模型(HuggingFace)将 Chunk 文本转为 512 维向量,用于语义匹配
向量存储FAISS 本地向量库构建并保存索引,实现快速相似度搜索
前端交互Streamlit提供简单直观的问答界面,支持用户输入与响应展示

模块之间的关系
向量数据库只保存向量 + 元数据,不保存完整语义;

每次用户输入时,实时提取向量、进行检索并构造上下文 Prompt;

构造后的 Prompt 被送入百炼智能体模型进行回复生成;

Streamlit 前端实时展示问答结果,形成闭环。

核心工具与模型选型

在本项目中,为了实现从 PDF 文档中提取段落,并基于语义进行匹配和问答的功能,选用了以下核心工具链与模型组件,确保系统具有较高的效率、准确性以及良好的可扩展性。
🧠 1. Embedding 模型:BAAI/bge-small-zh

项目内容
模型名称BAAI/bge-small-zh
模型来源HuggingFace @ BAAI
是否开源✅ 是
支持语言中文(优化)
部署方式本地部署(无需联网,免翻墙)
模型体积小型(约 120MB)
向量维度512
优势亮点轻量、高速、适配中文语义匹配任务
调用方式封装在 embedding.py 文件中,定义了 EmbeddingModel.embed_texts() 接口用于段落向量化处理

🧮 2. 向量数据库:FAISS

项目内容
名称FAISS(Facebook AI Similarity Search)
作用存储并检索高维向量(用于语义匹配)
部署方式本地离线,使用 .index.meta 文件存储数据
使用方式vector_store.py 中封装了 VectorStore 类,实现:
add() 向数据库添加向量与原始文本
save()/load() 存储与加载
search() 执行相似度检索
匹配方式L2 距离(欧氏距离)索引器 IndexFlatL2

📚 3. 文本切分工具:LangChain TextSplitter

项目内容
工具模块RecursiveCharacterTextSplitter
来源LangChain
作用将原始 PDF 文档内容切分成多个适配 embedding 的小段(chunk)
分段策略使用换行符、标点符号等多级分隔符,避免语义断裂
使用方式rag_chain.pyload_and_split_pdf() 中使用

🖼 5. 可视化界面框架:Streamlit

项目内容
框架名称Streamlit
用途构建简洁交互式 Web 应用界面
使用方式主入口文件 app.py,支持用户输入问题、展示检索段落与大模型生成回复

核心功能实现详解

📄 1. PDF 文档加载与切分
文件:rag_chain.py
函数:load_and_split_pdf()

✅ 实现目标:
将整本 PDF 文档切分为可用于语义匹配的文本段(chunk),避免段落过长或断句不清导致 embedding 表达质量下降。

🔍 2. 文本向量化(Embedding)
文件:embedding.py
类名:EmbeddingModel

✅ 实现目标:
将每段文本转为稠密语义向量(float32),便于后续进行语义匹配检索。

✅ 模型选型:
使用 HuggingFace 上的 BAAI/bge-small-zh 本地模型,支持中文语义精度较高。

🧠 3. 向量数据库构建与检索
文件:vector_store.py
类名:VectorStore

✅ 实现目标:
将文本对应的向量存入 FAISS 数据库;
支持向量相似度检索,返回与用户 query 最相近的段落及其元信息。

✅ 数据结构:
.index 文件:存储 FAISS 索引(支持快速相似度查询)
.meta 文件:存储 chunk 原文与元数据(如页码)

✅ 核心方法:
add(texts, vectors, metadatas)
save() / load()
search(query, top_k)

🤖 4. 问答生成:接入百炼智能体 API
文件:baichuan_llm.py
类名:DashScopeChatBot

✅ 实现目标:
组合用户问题与匹配段落,通过大模型生成符合上下文的回答。

项目目录结构说明

rag_demo/
├── app.py                  # 主入口,基于 Streamlit 的问答交互界面
├── embedding.py            # 文本向量化模块,封装 BGE-small-zh 模型
├── vector_store.py         # 自定义向量数据库类,基于 FAISS 实现
├── rag_chain.py            # 文档加载与切分工具,支持 PDF 预处理
├── baichuan_llm.py         # 调用百炼智能体(Bailian)生成问答内容
├── docs/                   # 存放用户上传或处理的 PDF 文档
│   └── example.pdf         # 示例 PDF 文件
├── faiss_index.index       # FAISS 索引文件(自动生成,保存向量索引)
├── faiss_index.meta        # FAISS 元信息文件(保存每段文本及页码)
└── README.md               # 项目说明文档

📌 各模块说明

文件 / 目录类型作用描述
app.py主程序启动 Streamlit 应用,支持用户输入与问答
embedding.py模型封装加载本地 BAAI/bge-small-zh 模型,并执行文本向量化
vector_store.py数据管理构建、查询、保存 FAISS 向量数据库
rag_chain.py工具模块加载 PDF 并使用智能分段切割为 chunk
baichuan_llm.py模型调用调用百炼智能体 API,生成基于文档内容的回答
docs/文档目录存放所有待处理的 PDF 文件
faiss_index.index索引数据FAISS 保存的向量索引二进制文件
faiss_index.meta元信息存储每段文本的原文及其元数据(如页码)
README.md文档项目的功能与使用说明

主要文件内容
app.py

import streamlit as st
from vector_store import VectorStore
import os# 设置页面标题
st.set_page_config(page_title="RAG 问答助手", layout="wide")# 标题
st.title("📄 PDF 语义搜索助手")
st.markdown("使用 FAISS + bge-small-zh 向量模型,实现 PDF 文档语义检索")# 加载向量库
@st.cache_resource
def load_vector_store():store = VectorStore()store.load()return store# 主入口
def main():store = load_vector_store()# 用户输入user_query = st.text_input("🔍 请输入你的问题:", placeholder="例如:番茄叶片检测方法有哪些?")# 查询结果if user_query:results = store.search(user_query, top_k=5)st.subheader("🔎 匹配结果")for i, (text, meta, score) in enumerate(results):with st.expander(f"结果 {i+1} |页面:{meta.get('page_label', '未知')} |得分:{score:.4f}"):st.write(text)# 运行主程序
if __name__ == "__main__":main()

embedding.py

from sentence_transformers import SentenceTransformer
from typing import List
import osclass EmbeddingModel:def __init__(self, model_name: str = "BAAI/bge-small-zh"):print("✅ 正在加载 embedding 模型,请稍候...")self.model = SentenceTransformer(model_name)print("✅ 模型加载完成:", model_name)def embed_texts(self, texts: List[str]) -> List[List[float]]:# 进行批量编码(text → vector)embeddings = self.model.encode(texts, show_progress_bar=True)return embeddings# ✅ 示例调用代码
if __name__ == "__main__":# 示例文本sample_texts = ["人工智能正在改变世界。","番茄叶片病虫害检测方法研究","LangChain 是一个强大的 RAG 框架。"]# 实例化模型embedder = EmbeddingModel()# 获取向量vectors = embedder.embed_texts(sample_texts)for i, vec in enumerate(vectors):print(f"\n🔹 文本 {i + 1} 的向量维度: {len(vec)},前 5 维预览: {vec[:5]}")

rag_chain.py

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
import osdef load_and_split_pdf(pdf_path: str, chunk_size=500, chunk_overlap=50):# 加载 PDF 文档(每一页为一个 Document)loader = PyPDFLoader(pdf_path)pages = loader.load()# 使用递归切割器分段(可按字符长度+换行符智能分段)splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size,chunk_overlap=chunk_overlap,separators=["\n\n", "\n", "。", "!", "?", ".", " ", ""])# 对每一页内容进行切割,保留 metadatadocuments = splitter.split_documents(pages)return documents# ✅ 测试运行入口
if __name__ == "__main__":test_pdf_path = r"D:\Desktop\AI\rag_demo\docs\RT-TLTR番茄叶片病虫害检测方法研究_胡成峰.pdf"  # 请确保路径正确if not os.path.exists(test_pdf_path):print(f"❌ 文件未找到:{test_pdf_path}")else:chunks = load_and_split_pdf(test_pdf_path)print(f"✅ 共切分出 {len(chunks)} 个段落\n")# 打印前 3 个 chunk 的内容与元信息for i, chunk in enumerate(chunks[:3]):print(f"🔹 Chunk {i + 1}")print("内容片段:", chunk.page_content[:200].replace("\n", " ") + "...")print("元信息:", chunk.metadata)print("-" * 60)

vector_store.py

import faiss
import os
import numpy as npimport pickle
from typing import List, Tuple
from embedding import EmbeddingModel
from langchain_core.documents import Documentclass VectorStore:def __init__(self, dim: int = 512, db_path: str = "faiss_index"):self.dim = dimself.db_path = db_pathself.index = faiss.IndexFlatL2(dim)  # L2 距离索引器self.texts = []      # 存储 chunk 原文self.metadatas = []  # 存储 chunk 的元信息def add(self, texts: List[str], vectors: List[List[float]], metadatas: List[dict]):self.index.add(np.array(vectors).astype("float32"))self.texts.extend(texts)self.metadatas.extend(metadatas)def save(self):faiss.write_index(self.index, f"{self.db_path}.index")with open(f"{self.db_path}.meta", "wb") as f:pickle.dump({"texts": self.texts, "metadatas": self.metadatas}, f)print(f"✅ 向量数据库已保存到:{self.db_path}.index / .meta")def load(self):self.index = faiss.read_index(f"{self.db_path}.index")with open(f"{self.db_path}.meta", "rb") as f:meta = pickle.load(f)self.texts = meta["texts"]self.metadatas = meta["metadatas"]print("✅ 向量数据库已加载")def search(self, query: str, top_k: int = 3) -> List[Tuple[str, dict, float]]:embedder = EmbeddingModel()query_vec = embedder.embed_texts([query])[0]D, I = self.index.search(np.array([query_vec]).astype("float32"), top_k)results = []for idx, dist in zip(I[0], D[0]):results.append((self.texts[idx], self.metadatas[idx], dist))return results# ✅ 测试入口
if __name__ == "__main__":import numpy as npfrom rag_chain import load_and_split_pdf# 1. 加载 PDF 并切分docs: List[Document] = load_and_split_pdf(r"D:\Desktop\AI\rag_demo\docs\RT-TLTR番茄叶片病虫害检测方法研究_胡成峰.pdf")texts = [doc.page_content for doc in docs]metadatas = [doc.metadata for doc in docs]# 2. 向量化embedder = EmbeddingModel()vectors = embedder.embed_texts(texts)# 3. 存入 FAISS 向量库store = VectorStore()store.add(texts, vectors, metadatas)store.save()# 4. 进行语义搜索store.load()results = store.search("番茄叶片检测方法")for i, (txt, meta, score) in enumerate(results):print(f"\n🔍 匹配结果 {i + 1}:")print("得分:", score)print("页面:", meta.get("page_label", "N/A"))print("内容片段:", txt[:200], "...")

查询算法解析:基于 FAISS 的 L2 距离向量检索

query_vec = embedder.embed_texts([query])[0]

功能:将用户输入的自然语言 query 通过 embedding 模型转化为一个向量(query_vec)。

底层模型:你使用的是 BAAI/bge-small-zh,输出的是一个 512 维的向量。

D, I = self.index.search(np.array([query_vec]).astype("float32"), top_k)

功能:调用 FAISS.IndexFlatL2 的 search() 方法,返回:

D:每个候选结果与 query 向量之间的 L2 距离(欧氏距离平方);

I:每个距离对应的原始文本在库中的索引位置。

底层算法:暴力遍历全部向量,通过 欧几里得距离(L2) 找出最近的 top_k 个向量

项目成果演示

📌 项目成果演示
本项目最终实现了一个可交互的 PDF 文档语义问答系统,集成了 Streamlit 页面、FAISS 向量数据库、中文 embedding 模型(bge-small-zh)和文档切分等组件,具备了完整的 RAG(Retrieval-Augmented Generation)基础架构。以下为演示亮点:
在这里插入图片描述

总结与思考

📌 项目总结
本项目以“从0搭建一个轻量级 RAG(Retrieval-Augmented Generation)语义搜索原型系统”为目标,围绕 LangChain 的文档处理工具链,结合 HuggingFace 本地向量化模型 BAAI/bge-small-zh 和高效的向量数据库 FAISS,完成了一个完整闭环的流程:

✅ 从 PDF 文档中提取文本并进行智能切分;
✅ 使用本地 embedding 模型对文本块进行向量化;
✅ 构建 FAISS 本地向量数据库,实现高效查询;
✅ 使用 Streamlit 实现简单而直观的交互页面;
✅ 支持全中文处理,部署门槛低、响应速度快、成本接近为零。

该项目在结构上清晰、功能上实用,非常适合初学者上手 RAG 系统构建,同时也具备进一步拓展 LLM 调用、问答生成、多文档多模态处理等能力的基础。

💡 学习思考
向量检索系统构建并不复杂,但细节决定效果:
文本的切分策略对匹配质量有很大影响;
向量化模型选择直接决定语义召回质量;
搜索算法(如使用 FAISS 的 L2 距离)虽简单,但结果解释性差,需合理展示。
本地部署是理解 RAG 的最好方式:
避免过度依赖 API,提升系统理解力;
便于调试 embedding 模型、切分器、检索逻辑等各个组件;
有助于构建对 embedding 语义空间的直觉认识。
国产 embedding 模型正在崛起:

像 bge-small-zh、acge_text_embedding、FlagEmbedding 等模型已经在中文语义匹配上达到非常好的效果;

对于轻量级任务,small 版本完全够用,延迟低、精度可接受。

向量数据库并不是黑盒:

像 FAISS 支持查看索引结构、手动添加/查询/删除向量;

元数据(如页面编号)可以与结果绑定,极大增强可解释性。

🚀 后续方向
集成 LLM,构建基于召回结果的回答生成(即真正的 RAG 问答);
支持多文档、多格式(txt/doc/html)的语义索引;
引入关键词过滤、正则抽取等补充匹配方式;
调整 UI,支持多轮对话式语义问答。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93047.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93047.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自主泊车算法

看我的git 在 open space 空间下规划出⼀条⾃⻋到停⻋位的⽆碰撞轨迹 满⾜平滑约束 可跟踪 考虑动态障碍物约束 在路径不可⽤的情况下 具备重规划能⼒ 重规划时能够做到⽆缝切换 即从原路径⽆缝切换到重规划路径 ⽆明显体感 规划频率 10HZ

USB 2.0 学习(2)- 连接

上回说到 usb的信号 k 状态和 j 状态,补充一下 usb的一些电气小知识。 1.USB设备有四根线 电源线VBus、 D、 D-、 地线GND 2.USB主机端的 D 和 D-各有1个15k下拉电阻,这是为了准确检测 D还是D-线上电平的变化 因为USB总线检测USB设备是低速还是全速设备…

解锁 Appium Inspector:移动端 UI 自动化定位的利器

​ 在移动端 UI 自动化测试中,元素定位是绕不开的核心环节。无论是 Android 还是 iOS 应用,能否精准、高效地定位到界面元素,直接决定了自动化脚本的稳定性和可维护性。而 Appium Inspector 作为 Appium 生态中专门用于元素定位的工具&#…

机器学习概念1

了解机器学习1、什么是机器学习机器学习是一门通过编程让计算机从数据中进行学习的科学 通用定义:机器学习是一个研究领域让计算机无须进行明确编程就具备学习能力 工程化定义:一个计算机程序利用经验E来学习任务T,性能是P,如果针…

前端html学习笔记5:框架、字符实体与 HTML5 新增标签

本文为个人学习总结,如有谬误欢迎指正。前端知识众多,后续将继续记录其他知识点! 目录 前言 一、框架标签 作用: 语法: 属性: 二、字符实体 作用: 三、html5新增标签 语义化 状态 列…

Day05 店铺营业状态设置 Redis

Redis 入门 Redis 简介 Redis 是一个基于内存的 key-value 结构数据库。 基于内存存储,读写性能高 适合存储热点数据(热点商品,资讯,新闻) 企业应用广泛 redis 中文网:Redis中文网 Redis 下载与安装 R…

Linux驱动开发probe字符设备的完整创建流程

一、 设备号分配1.静态分配通过register_chrdev_region预先指定设备号(需要确保未被占用)2.动态分配通过alloc_chrdev_region由内核自动分配主设备号,一般都是动态分配以避免冲突。3316 xxxx_dev.major 0; 3317 3318 if (xx…

生产环境中Spring Cloud Sleuth与Zipkin分布式链路追踪实战经验分享

生产环境中Spring Cloud Sleuth与Zipkin分布式链路追踪实战经验分享 在复杂的微服务架构中,服务调用链路繁杂,单点故障或性能瓶颈往往难以定位。本文结合真实生产环境案例,分享如何基于Spring Cloud Sleuth与Zipkin构建高可用、低开销的分布…

基于Python的《红楼梦》文本分析与机器学习应用

本文将详细介绍如何使用Python和机器学习技术对《红楼梦》进行深入的文本分析和处理,包括文本分卷、分词、停用词处理、TF-IDF特征提取以及文本可视化等关键技术。一、项目概述本项目的目标是对中国古典文学名著《红楼梦》进行全面的自动化处理和分析,主…

Bevy渲染引擎核心技术深度解析:架构、体积雾与Meshlet渲染

本文将深入探讨Bevy游戏引擎的渲染架构,重点分析其体积雾实现原理、Meshlet渲染技术以及基于物理的渲染(PBR)系统。内容严格基于技术实现细节,覆盖从底层渲染管线到高级特效的全套解决方案。一、Bevy渲染架构深度解析1.1 核心架构…

CASS11计算斜面面积

1.生成三角网2.工程应用--计算表面积--根据三角网

借助Rclone快速从阿里云OSS迁移到AWS S3

本文作者: 封磊 Eclicktech SA | AWS Community Builder DevTool | AWS UGL | 亚马逊云科技云博主 阿里云&InfoQ&CSDN签约作者 概述 随着企业云战略的调整和多云架构的普及,数据迁移成为了一个常见需求。本文将详细介绍如何使用Rclone工具,高效…

【入门系列】图像算法工程师如何入门计算机图形学?

作为图像算法工程师,入门计算机图形学(CG)有天然优势——你熟悉图像处理的像素级操作、数学工具(如矩阵运算)和优化思维,而图形学的核心目标(从3D信息生成2D图像)与图像处理有很强的…

淘宝API列表:高效获取商品详情图主图商品视频参数item_get

淘宝商品详情信息基本都是用图片展示的,制作精美,能更好的展示商品信息。如何通过API实现批量获取商品详情信息呢?1、在API平台注册账号,获取调用API的key和密钥。2、查看API文档,了解相关请求参数和返回参数。item_ge…

第23章,景深:技术综述

一,定义: 中景:物体聚焦的范围(即清晰成像的范围)。 景深:在中景之外,都会成像模糊,即景深。景深通常用来指示对场景的注意范围,并提供场景深度的感觉。 背景&#xff1a…

飞算 JavaAI -智慧城市项目实践:从交通协同到应急响应的全链路技术革新

免责声明:此篇文章所有内容都是本人实验,并非广告推广,并非抄袭,如有侵权,请联系。 目录 一、智慧城市核心场景的技术攻坚 1.1 交通信号智能优化系统的实时决策 1.1.1 实时车流数据处理与分析 1.1.2 动态信号配时…

GM3568JHF快速入门教程【二】FPGA+ARM异构开发板环境编译教程

SDK 可通过搭建好的 Docker 镜像环境进行编译。 具体参可考该部分文档内容。1 Docker镜像环境编译SDK1.1 SDK 自动编译命令切换到 Docker 内需要编译的 SDK 根目录,全自动编译默认是 Buildroot, 可以通过设置环境变量 RK_ROOTFS_SYSTEM 指定不同 rootfs.…

Vue3 整合高德地图完成搜索、定位、选址功能,已封装为组件开箱即用(最新)

Vue3 整合高德地图完成搜索、定位、选址功能(最新)1、效果演示2、前端代码2.1 .env.development2.2 GaodeMap.vue2.3使用示例1、效果演示 2、前端代码 2.1 .env.development https://console.amap.com/dev/key/app# 地图配置 VITE_AMAP_KEY "您的…

SpringBoot切换 Servlet 容器为Undertow

题目详细答案Spring Boot 默认使用 Tomcat 作为嵌入式的 Servlet 容器,但你也可以切换到 Undertow。Undertow 是一个轻量级、高性能的 Web 服务器和 Servlet 容器。步骤 1:排除 Tomcat 依赖需要在pom.xml文件(如果使用的是 Maven)…

通过限制对象的内存分配位置来实现特定的设计目标

《More Effective C》中的条款27聚焦于如何通过语言特性强制或禁止对象在堆上分配,其核心目标是通过控制内存分配位置来提升代码的安全性、可维护性和资源管理效率。 个人觉得,这个条款看看就可以了,可能在个别情况下需要考虑条款中说的情况。…