RAG 檢索增強生成架構深度解析
6 分鐘閱讀 1,100 字
RAG 檢索增強生成架構深度解析
大型語言模型(LLM)在知識截止日期之後的事件一無所知,也無法存取企業內部文件。檢索增強生成(Retrieval-Augmented Generation,RAG) 正是為了解決這個問題而生的架構模式。本文將從原理到實作,帶你完整了解 RAG 的設計思路。
RAG 的核心概念
RAG 的思路很直觀:在讓 LLM 回答問題之前,先從知識庫中找出相關的文件片段,然後把這些片段連同問題一起餵給模型,讓模型根據提供的上下文來回答。
整個流程分成兩個階段:
- 索引階段(Indexing):將文件切分、向量化並存入向量資料庫
- 查詢階段(Querying):將使用者問題向量化,檢索相似文件,組合成 Prompt 送給 LLM
索引階段詳解
文件載入與切分
from langchain.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 載入文件
loader = PyPDFLoader("company_handbook.pdf")
documents = loader.load()
# 切分文件
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每個片段的字元數
chunk_overlap=50, # 片段間的重疊字元數(保留上下文)
separators=["\n\n", "\n", "。", ",", " ", ""]
)
chunks = splitter.split_documents(documents)
print(f"切分成 {len(chunks)} 個片段")向量化與儲存
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 建立向量資料庫
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
vectorstore.persist()
print("向量資料庫建立完成")查詢階段詳解
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# 自訂 Prompt 模板
template = """你是一個專業的問答助手。請根據以下提供的背景資料來回答問題。
如果背景資料中沒有相關資訊,請誠實說明你不知道,不要編造答案。
背景資料:
{context}
問題:{question}
回答:"""
prompt = PromptTemplate(
input_variables=["context", "question"],
template=template
)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 4} # 取最相似的 4 個片段
),
chain_type_kwargs={"prompt": prompt},
return_source_documents=True
)
result = qa_chain({"query": "公司的年假政策是什麼?"})
print(result["result"])
print("\n來源文件:")
for doc in result["source_documents"]:
print(f"- {doc.metadata['source']}: {doc.page_content[:100]}...")進階技巧:混合檢索
純向量檢索有時會漏掉關鍵字完全匹配的文件。混合檢索結合向量相似度和 BM25 關鍵字搜尋,效果更好:
from langchain.retrievers import BM25Retriever, EnsembleRetriever
# BM25 關鍵字檢索
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 4
# 向量檢索
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# 混合:各佔 50% 權重
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.5, 0.5]
)重新排序(Reranking)
初步檢索出來的文件不一定按相關性排序,加入 Reranker 能大幅提升品質:
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank
compressor = CohereRerank(model="rerank-multilingual-v3.0", top_n=3)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=ensemble_retriever
)RAG 評估指標
如何衡量 RAG 系統的好壞?常用的評估框架 RAGAS 提供了以下指標:
| 指標 | 說明 |
|---|---|
| **Faithfulness** | 回答是否忠實於檢索到的文件 |
| **Answer Relevancy** | 回答是否切中問題 |
| **Context Precision** | 檢索到的文件是否都相關 |
| **Context Recall** | 相關文件是否都被檢索到 |
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy
results = evaluate(
dataset=test_dataset,
metrics=[faithfulness, answer_relevancy]
)
print(results)常見問題與解法
問題一:切分破壞語義完整性
解法:使用語義切分器,讓相同主題的內容保持在同一片段:
from langchain_experimental.text_splitter import SemanticChunker
splitter = SemanticChunker(
embeddings=embeddings,
breakpoint_threshold_type="percentile"
)問題二:問題與文件用詞不同
使用者可能問「請假規定」,但文件寫的是「休假辦法」。HyDE(假設文件嵌入) 可以解決這個問題:先讓 LLM 生成一個假設性的回答,用這個回答去檢索,效果往往更好。
問題三:上下文視窗限制
檢索太多文件會超出 LLM 的 context window。Map-Reduce 鏈式處理可以分批摘要後再整合:
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="map_reduce", # 分批處理每個文件再彙整
retriever=retriever
)實際部署建議
- 向量資料庫選擇:小型專案用 Chroma,生產環境考慮 Pinecone、Weaviate 或 pgvector
- 嵌入模型:中文效果較好的是
text-embedding-3-large或BAAI/bge-large-zh - 快取策略:相同問題的嵌入向量可以快取,避免重複 API 呼叫
- 非同步處理:索引大量文件時使用非同步批次處理
小結
RAG 架構讓 LLM 從「靜態知識庫」升級為「動態知識存取」的系統。掌握文件切分、向量檢索、混合搜尋、重新排序這幾個關鍵環節,就能建構出準確且可信賴的問答系統。從企業知識庫到客服機器人,RAG 正在成為 AI 應用的標準架構。
分享這篇文章