LangChain 教學:RAG 基本 Pipeline
簡介
在資訊爆炸的時代,單純的 大型語言模型 (LLM) 雖然擁有強大的生成能力,卻往往缺乏對最新或專業領域知識的即時存取。檢索增強生成(Retrieval‑Augmented Generation,簡稱 RAG)正是為了解決這個痛點而設計的技術:先從外部文件或向量資料庫中檢索相關片段,然後把檢索結果作為上下文交給 LLM,讓模型在產出答案時能夠「站在」正確的知識基礎上。
LangChain 作為 LLM 應用的開發框架,已經把 RAG 所需的 檢索器、文件載入器、向量嵌入模型、以及 LLM 呼叫等元件抽象化,讓開發者只需要組合幾個簡單的類別,就能快速搭建起一條完整的 RAG pipeline。本文將從概念說明、程式碼範例、常見陷阱與最佳實踐,最後延伸到實務應用場景,帶你一步步完成「基本 RAG Pipeline」的建置。
核心概念
1. RAG Pipeline 的四大步驟
| 步驟 | 功能說明 | LangChain 對應元件 |
|---|---|---|
| 文件載入 | 把原始資料(PDF、txt、網頁等)轉成文字 | DocumentLoader(如 PyPDFLoader、TextLoader) |
| 切分 & 向量化 | 把長文本切成較小的 Chunk,並產生向量嵌入 | TextSplitter + Embeddings(如 OpenAIEmbeddings) |
| 建立向量資料庫 | 把向量與原始文字一起儲存,支援相似度搜尋 | FAISS, Chroma, Pinecone 等 VectorStore |
| 檢索 & 生成 | 依據使用者查詢在向量庫中找出相關 Chunk,並把結果傳給 LLM 產生最終答案 | Retriever + LLMChain(或 ConversationalRetrievalChain) |
重點:在 LangChain 中,
Retriever負責 檢索,LLMChain負責 生成,兩者的組合即構成完整的 RAG 流程。
2. 文件載入與切分
2.1 載入 PDF 範例
from langchain.document_loaders import PyPDFLoader
# 讀取本機的 PDF 檔案
loader = PyPDFLoader("data/annual_report_2023.pdf")
documents = loader.load()
print(f"載入了 {len(documents)} 個頁面")
load()回傳的是List[Document],每個Document包含page_content(文字)與metadata(如頁碼、檔名)。
2.2 切分成 Chunk
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 設定每個 Chunk 最多 1000 個字,重疊 200 個字
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", " "]
)
chunks = splitter.split_documents(documents)
print(f"切分後共有 {len(chunks)} 個 Chunk")
技巧:
chunk_overlap可以避免重要資訊被切斷,提升檢索的召回率。
3. 向量化與向量資料庫
3.1 使用 OpenAI Embeddings
from langchain.embeddings import OpenAIEmbeddings
# 需要先在環境變數設定 OPENAI_API_KEY
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
3.2 建立 FAISS 向量庫
from langchain.vectorstores import FAISS
# 把文字 Chunk 與其向量一起存入 FAISS
vectorstore = FAISS.from_documents(chunks, embeddings)
# 檢查向量庫大小
print(f"FAISS 資料庫內有 {vectorstore.index.ntotal} 個向量")
FAISS 是本機端的向量搜尋引擎,適合開發階段或小型專案;如果要上線服務,可考慮雲端向量服務如 Pinecone、Weaviate。
4. 檢索與生成
4.1 建立 Retriever
# 這裡使用相似度搜尋 top_k=4
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
4.2 組合成完整的 RAG Chain
from langchain.llms import OpenAI
from langchain.chains import RetrievalQA
# LLM 設定(同樣需要 OPENAI_API_KEY)
llm = OpenAI(model_name="gpt-3.5-turbo", temperature=0)
# RetrievalQA 會自動把檢索結果拼接成 prompt,送給 LLM
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 直接把所有檢索結果塞入 prompt
retriever=retriever,
return_source_documents=True # 回傳原始 Chunk,方便除錯
)
# 測試問答
query = "2023 年公司營收的主要成長動力是什麼?"
result = qa_chain(query)
print("答案:", result["result"])
print("\n參考來源:")
for doc in result["source_documents"]:
print("- ", doc.metadata.get("source", "unknown"))
提示:
chain_type除了"stuff",還有"map_reduce"、"refine"等更進階的組合方式,可依需求調整。
5. 完整範例:從檔案到問答的端到端流程
# 1️⃣ 載入文件
loader = PyPDFLoader("data/annual_report_2023.pdf")
documents = loader.load()
# 2️⃣ 切分
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = splitter.split_documents(documents)
# 3️⃣ 向量化 & 建立向量庫
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
vectorstore = FAISS.from_documents(chunks, embeddings)
# 4️⃣ 建立 Retriever
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# 5️⃣ 組合 RetrievalQA
llm = OpenAI(model_name="gpt-3.5-turbo", temperature=0)
qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever)
# 6️⃣ 問答測試
while True:
query = input("\n請輸入問題 (enter 離開): ")
if not query:
break
answer = qa(query)
print("\n🗨️ 答案:", answer["result"])
print("\n🔎 參考來源:")
for doc in answer["source_documents"]:
print(f" - {doc.metadata.get('source', 'unknown')} (第 {doc.metadata.get('page', '?')} 頁)")
只要把
data/annual_report_2023.pdf換成自己的檔案,即可快速得到一個 可互動的問答介面。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 最佳實踐 |
|---|---|---|
| Chunk 大小過小 | 會導致檢索結果缺乏足夠上下文,LLM 產生的答案可能斷裂。 | 依據模型上下文長度(如 GPT‑3.5 為 4k token)設定 chunk_size,通常在 800~1500 字之間較佳。 |
| 向量模型與 LLM 不匹配 | 使用不同領域的嵌入模型(如科學領域的專屬模型)卻搭配通用 LLM,檢索結果可能不相關。 | 盡量選擇 與 LLM 兼容的嵌入模型(如 OpenAI text-embedding-ada-002),或自行微調向量模型。 |
檢索結果過多 (k 設太大) |
會把太多不相關的文字塞進 prompt,超過 token 限制,甚至降低答案品質。 | 先從 k=3~5 起步,觀察效果後再微調;可使用 重排 (re‑ranking) 進一步篩選。 |
| 忘記傳遞 Metadata | 若要在答案中標示來源,必須在 Document 的 metadata 中保留檔名、頁碼等資訊。 |
載入時使用 metadata,或在切分後手動加入 metadata["source"]、metadata["page"]。 |
| 向量庫未持久化 | 開發階段每次重啟程式都重新建庫,浪費時間。 | 使用 FAISS.save_local() 或雲端向量服務的持久化功能,讓向量庫只建一次。 |
實際應用場景
- 企業內部知識庫
- 把公司手冊、會議記錄、技術文件匯入,員工只要輸入問題,即可即時取得最新的官方答案。
- 客服支援
- 結合 FAQ、產品說明書,讓聊天機器人提供 參考來源 的精準回覆,降低誤導風險。
- 法律或醫療文件檢索
- 針對大量條款或病例報告,RAG 能在保密前提下快速定位相關條文,協助專業人員決策。
- 教育平台
- 把教材、課程筆記轉成向量庫,學生提問時可得到結合課本內容的個性化解答。
- 多語言文件搜尋
- 透過多語言嵌入模型(如
sentence-transformers),同時支援中文、英文等語言的跨語檢索。
- 透過多語言嵌入模型(如
總結
檢索增強生成(RAG)是將 外部知識 與 大型語言模型 有機結合的關鍵技術。透過 LangChain,我們只需要:
- 載入 & 切分 文件
- 向量化 並建立 向量資料庫
- 設定 Retriever 取得相關 Chunk
- 將檢索結果交給 LLM 完成生成
只要掌握上述四個步驟,並留意 Chunk 大小、k 值、向量模型匹配 等細節,即可在短時間內打造出 可靠、可追溯 的問答系統。未來,隨著向量資料庫服務與嵌入模型的持續進化,RAG 的應用範圍將更加廣闊,從企業內部到跨領域的知識服務,都能受惠於這條 基本 RAG Pipeline。
祝你在 LangChain 的旅程中,玩得開心、寫得順手! 🎉