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
英文地址,請注明出處:http://en.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聚焦于如何通過語言特性強制或禁止對象在堆上分配,其核心目標是通過控制內存分配位置來提升代碼的安全性、可維護性和資源管理效率。 個人覺得,這個條款看看就可以了,可能在個別情況下需要考慮條款中說的情況。…