Day 4 我們理解了 Embedding
我們知道文字可以透過 Embedding Model 轉換成向量
也知道使用者的問題同樣可以轉換成 Vector,接著透過相似度搜尋,找到與問題最相關的文件內容
但目前這還不能稱為一個真正的 RAG 系統
因為我們還缺少一個非常重要的要素
Vector Database (向量資料庫)
今天就來實際建立 Vector Database,讓我們的 AI 助理第一次真正具備:
「搜尋自己的知識庫」
假設我們今天有一份文件:
本產品支援 Windows 10、Windows 11 以及 Ubuntu 22.04 與 Ubuntu 24.04
使用者可以透過官方網站下載安裝程式
安裝完成後,需要重新啟動系統
經過 Day 3 的 Chunking
Chunk 1
本產品支援 Windows 10、Windows 11 以及 Ubuntu 22.04 與 Ubuntu 24.04。
Chunk 2
使用者可以透過官方網站下載安裝程式。
Chunk 3
安裝完成後,需要重新啟動系統。
再經過 Day 4 的 Embedding
Chunk 1 → Vector 1
Chunk 2 → Vector 2
Chunk 3 → Vector 3
問題來了:
這些 Vector 要放在哪裡
如果只是存在變數裡,程式一關閉,資料就消失了
而且當文件一堆資訊
我們也不可能每次都重新把所有資料處理一次
因此,我們需要一個專門儲存與搜尋向量的系統
這就是:
Vector Database
一個 Vector Database 中,通常不會只有 Vector
實際上我們會一起儲存向量 + 原始文字
這樣當搜尋找到某個 Vector 時,我們才能知道:
「這個 Vector 原本是哪一段文字」
這是一個很容易混淆的地方
一般資料庫比較擅長具有明確條件的資料查詢
但是 RAG 的問題通常不是這麼明確
使用者可能問:
「這個產品可以在哪些作業系統上使用」
我們不一定知道答案中會出現什麼
甚至使用者可能問:
「這套系統能不能裝在微軟最新的桌面作業系統」
這時候單純做關鍵字比對可能不夠
Vector Database 可以透過向量相似度進行:
Semantic Search (語意搜尋)
假設文件中有:
本產品支援 Windows 11
使用者問:
「這個軟體可以裝在哪個作業系統」
搜尋:
軟體
作業系統
文件裡不一定有完全相同的詞
因此可能找不到
使用 Embedding 即使文字不完全相同,只要語意足夠接近,就有機會被搜尋出來
這就是 Vector Database 在 RAG 裡的重要性
今天我們使用:
Chroma
作為本地端的 Vector Database
原因很簡單:
今天的目標不是探討所有 Vector Database
而是先真正完成整體架構
首先安裝:
pip install chromadb sentence-transformers
如果 Day 3 已經安裝:
langchain-text-splitters
則可以繼續使用
建立:
import chromadb
client = chromadb.PersistentClient(
path="./data/chroma"
)
這裡使用:
PersistentClient
代表資料會儲存在本機
程式關閉之後,Vector Database 的資料仍然存在
接著建立一個 Collection:
collection = client.get_or_create_collection(
name="knowledge_base"
)
可以把 Collection 想成一個資料集合
未來如果有不同知識領域,也可以建立不同 Collection
延續 Day 3 的:
內容:
本產品支援 Windows 10、Windows 11 以及 Ubuntu 22.04 與 Ubuntu 24.04
使用者可以透過官方網站下載安裝程式
安裝完成後,需要重新啟動系統
先讀取:
with open(
"data/sample.txt",
"r",
encoding="utf-8"
) as f:
text = f.read()
使用 Day 3 的方式:
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=100,
chunk_overlap=20
)
chunks = splitter.split_text(text)
實際 Chunk 數量會依文字內容與切割設定而有所不同
接著使用 Day 4 的 Embedding:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer(
"all-MiniLM-L6-v2"
)
將 Chunk 轉換成 Vector:
embeddings = model.encode(chunks)
現在:
Chunk 1 → Vector 1
Chunk 2 → Vector 2
Chunk 3 → Vector 3
接著把資料加入 Vector Database:
collection.add(
ids=[f"chunk-{i}" for i in range(len(chunks))],
documents=chunks,
embeddings=embeddings.tolist(),
metadatas=[
{
"source": "sample.txt"
}
for _ in chunks
]
)
這時候資料就真正進入 Chroma
把前面的內容整合起來:
import chromadb
from langchain_text_splitters import RecursiveCharacterTextSplitter
from sentence_transformers import SentenceTransformer
# 讀取文件
with open(
"data/sample.txt",
"r",
encoding="utf-8"
) as f:
text = f.read()
# Chunking
splitter = RecursiveCharacterTextSplitter(
chunk_size=100,
chunk_overlap=20
)
chunks = splitter.split_text(text)
# Embedding Model
model = SentenceTransformer(
"all-MiniLM-L6-v2"
)
embeddings = model.encode(chunks)
# 建立 Chroma
client = chromadb.PersistentClient(
path="./data/chroma"
)
collection = client.get_or_create_collection(
name="knowledge_base"
)
# 儲存資料
collection.add(
ids=[
f"chunk-{i}"
for i in range(len(chunks))
],
documents=chunks,
embeddings=embeddings.tolist(),
metadatas=[
{
"source": "sample.txt"
}
for _ in chunks
]
)
print(f"成功建立 {len(chunks)} 個 Chunks")
如果成功,可以看到:
成功建立 3 個 Chunks
實際數量會依 Chunking 設定而不同
前面完成的是建立 Knowledge Base
現在開始做 RAG 的另一半:
假設使用者問:
query = "這個產品支援哪些作業系統"
先轉成 Embedding
接著搜尋 Chroma
results = collection.query(
query_embeddings=query_embedding.tolist(),
n_results=2
)
代表希望取得最相關的 2 個結果
加入:
print(results["documents"])
可能得到:
[
[
"本產品支援 Windows 10、Windows 11...",
"以及 Ubuntu 22.04 與 Ubuntu 24.04..."
]
]
這就是我們第一次真正完成:
「搜尋自己的知識庫」
這裡最重要的是:
我們還沒有讓 LLM 回答
目前系統只能做到:
「幫我找相關資料」
但這其實已經是 RAG 非常重要的一半
RAG 的名字其實已經告訴我們它包含兩件事情 Retrieval + Generation
負責:
找資料
負責:
根據資料產生答案
因為我們希望把 RAG 拆開
如果今天直接使用
可能只需要幾行程式就可以完成
但我們可能不知道:
Chunk 是怎麼來的
Vector 是怎麼產生的
Search 是怎麼找到資料的
為什麼這個 Chunk 被找到
因此這 30 天的目標不是:
「用最少的程式碼做出 RAG」
而是:
「真正理解一個 AI Application 是怎麼組成的」
這個概念通常可以理解為:
Top-K Retrieval
也就是:
找出最相關的 K 個 Chunk
都會提供給後面的 LLM
假設:
K = 1
可能發生:
只找到一小段資訊 + Context 不完整
但如果:
K = 20
又可能變成大量無關內容 + Context 增加 + Token 增加 + LLM 負擔增加
因此:
Top-K 需要根據實際資料進行實驗
後面可以進一步測試,觀察不同 K 值對 Retrieval 與回答品質的影響
前面我們存入:
metadatas=[
{
"source": "sample.txt"
}
]
這就是 Metadata
實際 RAG 系統可能包含:
{
"source": "product_manual.pdf",
"page": 12,
"section": "System Requirements",
"document_type": "manual"
}
這些資訊可以幫助我們:
答案來自哪一份文件
PDF 第幾頁
例如:
只搜尋產品手冊
而不是:
所有文件
因此一個成熟的 RAG 系統,不應該只關心 Chunk + Vector
也需要好好設計 Metadata
如果把前幾天串起來:
Chunking
Vector Embedding
Similarity Search
我們已經完成 RAG 的資料檢索基礎
今天可以實際測試不同問題
這個產品支援哪些作業系統
預期找到:
Windows
Ubuntu
安裝完成後需要做什麼
預期找到:
安裝完成後,需要重新啟動系統
要去哪裡下載安裝程式
預期找到:
使用者可以透過官方網站下載安裝程式
這個產品支援 macOS 嗎
這是一個很重要的測試。
因為文件中並沒有:
macOS
我們要觀察:
Vector Search 會找到什麼
以及:
如果沒有足夠證據,後面的 AI 能不能正確回答「文件沒有提供相關資訊」
這會在後面的 RAG Evaluation 控制中繼續處理
今天我們第一次建立了一個真正可以工作的 Vector Database
我們也理解了幾個重要概念:
而目前的 AI 助理已經開始具備一個很重要的能力:
它不只會回答問題,而是開始懂得「去哪裡找資料」
我們還沒有讓 LLM 根據這些資料回答
這時候,我們才會真正完成:
第一個可以根據自己的知識庫回答問題的 RAG Chatbot
[Day 6] 我們就把 Vector Search 與 LLM 串起來,讓 AI 助理真正「看著資料回答問題」