前言
如果你曾經問過 ChatGPT 最新的新聞,它會告訴你它的知識有截止日期(knowledge cutoff)。
如果你問它你公司內部的文件,它完全不知道。
這是大型語言模型(LLM)的根本限制:訓練資料是靜態的。
RAG(Retrieval-Augmented Generation) 就是解決這個問題的主流方法。它讓 LLM 在回答前先「查資料」,就像一個學生考試時可以翻開參考書——而不是完全靠記憶。
這個系列共五篇,帶你從基礎到進階,完整掌握 RAG 的設計與優化。
為什麼 LLM 需要 RAG?
LLM 的三大知識限制
| 問題 | 說明 |
|---|---|
| 知識截止日期 | 模型只知道訓練時間點之前的資訊 |
| 無法存取私有資料 | 公司內部文件、資料庫、個人筆記都不在訓練集裡 |
| 幻覺(Hallucination) | 對不確定的問題,模型會「編造」聽起來合理的答案 |
解法比較
方案 A:Fine-tuning(微調)
優點:模型真正「學會」知識
缺點:成本高、資料需要大量、難以更新、模型大小增加
方案 B:RAG(檢索增強生成)
優點:即時更新、成本低、可追溯來源
缺點:需要維護向量資料庫、回答品質受檢索品質影響
結論:對大多數企業應用,RAG 是更實際的選擇。Fine-tuning 適合改變模型「風格」或「推理方式」,不適合注入大量知識。
RAG 的核心架構
一個標準的 RAG 系統分成兩個主要流程:
1. 索引流程(Indexing Pipeline)— 離線執行
原始文件(PDF、Word、網頁)
↓
文字擷取(Text Extraction)
↓
切塊(Chunking)— 將長文件切成小片段
↓
向量化(Embedding)— 將文字轉成數字向量
↓
存入向量資料庫(Vector Store)
2. 查詢流程(Query Pipeline)— 即時執行
使用者問題(Query)
↓
向量化(Query Embedding)
↓
向量搜尋(Similarity Search)— 找出最相關的文件片段
↓
組合 Prompt(Context + Question)
↓
LLM 生成答案(Generation)
↓
回傳給使用者
這個最基本的架構被稱為 Naive RAG(樸素 RAG)。
核心概念解釋
Embedding(向量嵌入)
Embedding 是把文字轉成一串數字(向量)的過程。
語意相近的文字,它們的向量在空間中也會很接近。
1# 概念示意
2"貓喜歡睡覺" → [0.12, -0.34, 0.87, ...] # 768 維向量
3"狗喜歡跑步" → [0.15, -0.31, 0.72, ...] # 語意不同,但都是動物,有些維度接近
4"量子物理" → [-0.88, 0.92, -0.11, ...] # 語意差很多,向量距離遠
向量相似度
最常用的相似度計算方式是餘弦相似度(Cosine Similarity):
1import numpy as np
2
3def cosine_similarity(vec_a, vec_b):
4 dot = np.dot(vec_a, vec_b)
5 norm = np.linalg.norm(vec_a) * np.linalg.norm(vec_b)
6 return dot / norm
7
8# 值域 -1 到 1,越接近 1 表示越相似
Chunking(切塊)
為什麼要切塊?
- LLM 的 context window 有限制
- 太長的文件會「稀釋」相關資訊,降低搜尋精準度
- 讓每個 chunk 專注在單一主題,提升匹配品質
實作:用 Python 建立你的第一個 RAG
環境安裝
1pip install openai chromadb tiktoken langchain-text-splitters
完整範例程式碼
1import openai
2import chromadb
3from langchain_text_splitters import RecursiveCharacterTextSplitter
4
5# ---- 設定 ----
6client = openai.OpenAI(api_key="your-api-key")
7chroma_client = chromadb.Client()
8collection = chroma_client.create_collection("my_docs")
9
10EMBED_MODEL = "text-embedding-3-small"
11CHAT_MODEL = "gpt-4o-mini"
12
13# ---- 工具函式 ----
14
15def get_embedding(text: str) -> list[float]:
16 """將文字轉成向量"""
17 response = client.embeddings.create(input=text, model=EMBED_MODEL)
18 return response.data[0].embedding
19
20
21def index_documents(docs: list[str]) -> None:
22 """索引流程:切塊 → 向量化 → 存入向量 DB"""
23 splitter = RecursiveCharacterTextSplitter(
24 chunk_size=500, # 每塊最多 500 字元
25 chunk_overlap=50, # 前後重疊 50 字元,避免斷句
26 )
27
28 all_chunks = []
29 for doc in docs:
30 chunks = splitter.split_text(doc)
31 all_chunks.extend(chunks)
32
33 embeddings = [get_embedding(chunk) for chunk in all_chunks]
34
35 collection.add(
36 documents=all_chunks,
37 embeddings=embeddings,
38 ids=[f"chunk_{i}" for i in range(len(all_chunks))],
39 )
40 print(f"✅ 已索引 {len(all_chunks)} 個 chunks")
41
42
43def retrieve(query: str, top_k: int = 3) -> list[str]:
44 """查詢流程:向量搜尋,取出最相關的 chunks"""
45 query_embedding = get_embedding(query)
46 results = collection.query(
47 query_embeddings=[query_embedding],
48 n_results=top_k,
49 )
50 return results["documents"][0] # 回傳 top_k 個文字片段
51
52
53def generate_answer(query: str, context_chunks: list[str]) -> str:
54 """組合 Prompt,讓 LLM 根據 context 回答"""
55 context = "\n\n---\n\n".join(context_chunks)
56
57 prompt = f"""你是一個知識庫問答助手。請根據以下【參考資料】回答【問題】。
58如果參考資料中沒有足夠資訊,請直接說「根據現有資料無法回答」,不要自行推測。
59
60【參考資料】
61{context}
62
63【問題】
64{query}
65
66【回答】"""
67
68 response = client.chat.completions.create(
69 model=CHAT_MODEL,
70 messages=[{"role": "user", "content": prompt}],
71 temperature=0, # RAG 場景建議設低,減少創意發揮
72 )
73 return response.choices[0].message.content
74
75
76# ---- 主程式 ----
77
78def rag_pipeline(query: str) -> str:
79 """完整的 RAG pipeline"""
80 # Step 1: 檢索
81 relevant_chunks = retrieve(query, top_k=3)
82
83 # Step 2: 生成
84 answer = generate_answer(query, relevant_chunks)
85
86 return answer
87
88
89# ---- 示範 ----
90
91if __name__ == "__main__":
92 # 準備知識文件(實際場景可以是 PDF、Markdown、資料庫內容)
93 documents = [
94 """
95 Python 是一種高階、直譯式程式語言,由 Guido van Rossum 在 1991 年發布。
96 Python 的設計哲學強調程式碼的可讀性,使用縮排來表示程式碼區塊。
97 Python 廣泛應用於資料科學、機器學習、網頁開發和自動化腳本。
98 """,
99 """
100 向量資料庫是專門儲存和搜尋向量嵌入的資料庫系統。
101 常見的向量資料庫包括:Chroma、Pinecone、Weaviate、Qdrant、Milvus。
102 向量資料庫使用近似最近鄰搜尋(ANN)演算法,可以在百萬筆資料中快速找到最相似的向量。
103 """,
104 """
105 RAG(Retrieval-Augmented Generation)是一種結合資訊檢索與文字生成的 AI 架構。
106 RAG 的主要優點是可以讓 LLM 存取外部知識庫,解決模型知識過時的問題。
107 RAG 系統通常由三個部分組成:文件索引、相似度搜尋、語言模型生成。
108 """,
109 ]
110
111 # 索引文件
112 index_documents(documents)
113
114 # 查詢
115 questions = [
116 "RAG 有哪些主要優點?",
117 "有哪些常見的向量資料庫?",
118 "Python 是誰發明的?",
119 ]
120
121 for q in questions:
122 print(f"\n❓ 問題:{q}")
123 answer = rag_pipeline(q)
124 print(f"💡 回答:{answer}")
預期輸出
✅ 已索引 3 個 chunks
❓ 問題:RAG 有哪些主要優點?
💡 回答:根據參考資料,RAG 的主要優點是可以讓 LLM 存取外部知識庫,解決模型知識過時的問題。
❓ 問題:有哪些常見的向量資料庫?
💡 回答:常見的向量資料庫包括:Chroma、Pinecone、Weaviate、Qdrant、Milvus。
❓ 問題:Python 是誰發明的?
💡 回答:Python 是由 Guido van Rossum 發明的,並於 1991 年發布。
Naive RAG 的局限性
這個基本實作已經可以運作,但在實際應用中會碰到幾個問題:
| 問題 | 現象 | 後續篇章 |
|---|---|---|
| Chunking 策略粗糙 | 語意被切斷,搜尋精準度低 | 第二篇 |
| 只有語意搜尋 | 關鍵字搜尋效果有時更好 | 第三篇(混合搜尋) |
| 單次查詢不夠 | 複雜問題需要多次查詢才能拼湊完整答案 | 第三篇(Multi-Query) |
| 沒有 Reranking | Top-K 結果可能不是最相關的 | 第三篇(Reranker) |
| Context 太長 | 塞入過多不相關 chunk,LLM 反而混淆 | 第四篇(Context Compression) |
| 沒有評估指標 | 不知道 RAG 品質好不好 | 第五篇 |
小結
這篇介紹了:
- 為什麼需要 RAG:LLM 的知識限制
- RAG 的核心架構:索引流程 vs 查詢流程
- 關鍵概念:Embedding、向量相似度、Chunking
- 第一個 RAG 實作:用 ChromaDB + OpenAI 建立完整 pipeline
下一篇我們會深入探討 Chunking 策略與向量資料庫的選型,讓 RAG 的基礎打得更扎實。
系列導覽