基本 RAG 實作:逐步教學
這篇適合初學者的教學會說明基本 RAG 實作的運作方式,介紹最佳實踐,並逐步帶你使用 Python、ChromaDB 以及 Perplexity 的嵌入模型和語言模型,構建一個可運作的命令列 RAG 系統。

RAG 是現代以 LLM 為基礎的基礎設施中不可或缺的組件。RAG 讓 AI 模型可以存取外部數據,有助解決幻覺及訓練數據不完整等問題。這亦意味著企業可以使用載有敏感或專有資訊的內部知識庫。
RAG 的實作可以非常複雜,但要構建一個基本的 RAG 系統卻出奇地容易。對初學者而言,建立一個簡單的架構,可以清楚展示 RAG 在現實中如何運作,並為進一步的實驗打好基礎。
在本文中,我們會勾勒出基本 RAG 實作的藍圖,介紹最佳實踐,然後使用 Python、ChromaDB 及兩個 Perplexity 模型(分別用於嵌入和生成答案)構建一個可運作的系統。
基本 RAG 實作流程:概覽
基本的 RAG 系統有三個主要組件:協調器(orchestrator)、資訊來源及 LLM。在典型的工作流程中,用戶輸入提示後,協調器會修改提示,以搜尋所有已連接的資訊來源。擷取到的資訊隨後會加入原本的輸入上下文,再傳送至 LLM 以生成回覆。
構建 RAG 實作時,你需要考慮以下步驟:
- 索引和嵌入:在這個階段,你的文件會被轉換為協調器可搜尋的資訊來源。最常見的做法之一是建立可搜尋的向量索引。我們會使用 Perplexity 的專用模型,將你的文件區塊轉換為嵌入向量,然後用熱門的開源向量數據庫 ChromaDB 儲存這些嵌入向量。
- 協調:這是位於外部資訊來源(在本例中為向量數據庫)與 LLM 之間的應用程式。我們會建立一個簡單的 Python 指令碼作為協調器。
- 擷取和生成:當用戶透過聊天介面提問時,協調器會接收問題、執行向量搜尋,並將擴充後的輸入傳送給 LLM。我們會使用命令列向 Python 協調器發出提示並與之溝通,而不會另外構建專用前端。
最佳實踐:怎樣才算是有效的 RAG 實作?
RAG 架構有多種類型,不同的用例需要不同的方法。儘管如此,有不少最佳實踐適用於所有情況,並能確保系統準確、具成本效益、可擴展且易於維護。
以下最佳實踐適用於所有類型的 RAG 實作:
- 確保數據匯入管道乾淨:建立索引前,來源數據應盡可能乾淨。你亦應使用適合你內容的嵌入模型。
- 使用混合搜尋:最佳的擷取方法因用例而異,無論是向量數據庫搜尋、基於 API 的網絡搜尋、知識圖譜遍歷、關鍵字搜尋或其他方法皆然。選擇合適的組合對於盡量降低成本十分重要。
- 評估你的系統:實作完成後,你應從三個核心指標進行評估:準確度、相關性及延遲。準確度是指事實是否正確;相關性是指擷取到的資訊與用戶查詢的切合程度;延遲則是指擷取資訊的速度。
- 安全處理資訊:如果你的 RAG 系統使用私人內部資訊,請確保這些資訊安全儲存,並妥善管理系統介面的存取權限。切勿分享 API 金鑰。
基本 RAG 實作:7 步教學
在本教學中,我們會構建一個命令列應用程式,用來回答有關單一文件的問題。我們會使用 Python、ChromaDB 及 Perplexity API。
本教學的目的並非構建可用於生產環境的 RAG 系統,而是提供一個適合初學者的教學,說明基本的 RAG 架構在實際中如何運作。
第 1 步:安裝軟件
請確保你的電腦已安裝以下項目,並且你已擁有 Perplexity API 帳戶:
- Python:這是我們用來構建協調器的程式語言,協調器負責管理嵌入模型、ChromaDB 與 Perplexity 之間的數據流。
- Visual Studio:我們會使用 Visual Studio 作為程式碼編輯器。安裝後,開啟 Extensions(擴充功能)面板並下載 Python 擴充功能。
- Perplexity API 帳戶:Perplexity 會同時提供嵌入模型(將你的數據轉換為向量嵌入)及處理擴充提示的 LLM。你毋須付費帳戶,但會耗用少量點數。
第 2 步:建立專案資料夾
首先建立一個名為 rag-project 的資料夾,然後選擇 File > Open Folder,在 VS Code 中開啟此資料夾。接著透過 Terminal > New Terminal 開啟整合式終端機。
輸入以下指令以建立 Python 虛擬環境:
py -3.12 -m venv .venv你現在需要安裝四個 Python 程式庫:perplexityai、chromadb、python-dotenv 及 numpy。Perplexity SDK 讓你透過 API 連接 Perplexity 的模型。ChromaDB 負責建立向量數據庫,並會在本機執行。Python-dotenv 會從本機檔案安全載入你的 API 金鑰,而 NumPy 則是匯入過程所需。
使用以下指令安裝這些程式庫:
.\.venv\Scripts\python.exe -m pip install perplexityai chromadb python-dotenv numpy第 3 步:設定檔案
將以下檔案加入 rag-project(專案根目錄),方法是在 Explorer 側邊欄選擇 New File…:
- index.py:這是為你的文件建立索引的應用程式。你可以再次執行它以加入更多文字。
- rag.py:這是將擔任協調器的 Python 應用程式。
- document.txt:這個檔案將載有你要轉換為向量數據庫的文件內容。
- .env:你會在這裡存放你的 Perplexity API 金鑰。
在 .env 中輸入以下內容:
PERPLEXITY_API_KEY=your_api_key_here將 your_api_key_here 替換為你的實際金鑰。為此,你需要前往 Perplexity API Console 的首頁儀表板,然後選擇 Create an API key。請妥善保管此 API 金鑰,切勿外洩。
我們會使用兩個模型:Perplexity 的 pplx-embed-v1-0.6b,用於在上傳至 ChromaDB 前嵌入我們的文件內容;以及 GPT-5.6-Luna(透過 Perplexity 的 API 存取),用於處理擷取到的資訊。
在 document.txt 中,加入你的 RAG 管道將會擷取的文字。如果你手邊沒有文件,可以在本教學中使用 Project Gutenberg 上 William Blake 的 Songs of Innocence and of Experience(《天真與經驗之歌》)。這是一篇簡短易懂、屬於公有領域的文本。
第 4 步:編寫索引程式
將以下程式碼複製並貼上至 index.py。每個小標題都會簡要說明其後程式碼片段的作用。這個程式會使用 Perplexity 的嵌入模型 pplx-embed-v1-0.6b 及 NumPy 程式庫,將你的來源資料轉換為向量嵌入,並儲存至 ChromaDB 數據庫。
這個程式會執行以下操作:
- 開啟 document.txt。
- 將文字分割成互相重疊的區塊。
- 將這些區塊傳送至 Perplexity 的 pplx-embed-v1-0.6b 嵌入模型。
- 使用 NumPy(一個提供運算函數的 Python 程式庫)解碼並正規化傳回的向量。
- 將向量及原始區塊儲存至 ChromaDB 數據庫(你會看到它出現在專案目錄中)。
程式已設定為不會在每次執行時重新為文件建立索引。如果你在 document.txt 中加入更多資訊,它只會嵌入新增的文字。
from pathlib import Path
from collections import defaultdict, deque
from hashlib import sha256
import base64
import numpy as np
import chromadb
from dotenv import load_dotenv
from perplexity import Perplexity
# 1. Connect to Perplexity and ChromaDB
load_dotenv()
ai = Perplexity()
db = chromadb.PersistentClient(path="./rag_database_perplexity")
collection = db.get_or_create_collection(
name="documents",
metadata={"hnsw:space": "cosine"}
)
# Convert Perplexity's encoded embeddings into numerical vectors
def decode_embedding(encoded):
vector = np.frombuffer(base64.b64decode(encoded), dtype=np.int8).astype(np.float32)
norm = np.linalg.norm(vector)
if norm == 0:
raise ValueError("Received a zero-length embedding vector.")
return (vector / norm).tolist()
# 2. Read the document and split it into chunks
text = Path("document.txt").read_text(encoding="utf-8")
words = text.split()
chunk_size = 180
overlap = 30
step = chunk_size - overlap
chunks = [" ".join(words[i:i + chunk_size]) for i in range(0, len(words), step)]
if not chunks:
raise ValueError("document.txt is empty.")
# 3. Retrieve the chunks already stored in ChromaDB
existing = collection.get(include=["documents"])
stored_chunks = defaultdict(deque)
existing_ids = set(existing["ids"])
for chunk_id, document in zip(existing["ids"], existing["documents"]):
stored_chunks[document].append(chunk_id)
# 4. Identify unchanged and new chunks
new_chunks = []
new_ids = []
kept_ids = set()
unchanged = 0
for chunk in chunks:
if stored_chunks[chunk]:
chunk_id = stored_chunks[chunk].popleft()
kept_ids.add(chunk_id)
unchanged += 1
else:
chunk_hash = sha256(chunk.encode("utf-8")).hexdigest()
chunk_id = f"chunk-{chunk_hash}"
suffix = 1
while chunk_id in existing_ids or chunk_id in kept_ids or chunk_id in new_ids:
chunk_id = f"chunk-{chunk_hash}-{suffix}"
suffix += 1
new_chunks.append(chunk)
new_ids.append(chunk_id)
kept_ids.add(chunk_id)
# 5. Generate embeddings in batches of up to 100 chunks
batch_size = 100
for i in range(0, len(new_chunks), batch_size):
batch_chunks = new_chunks[i:i + batch_size]
batch_ids = new_ids[i:i + batch_size]
embeddings = ai.embeddings.create(
model="pplx-embed-v1-0.6b",
input=batch_chunks,
encoding_format="base64_int8"
)
vectors = [decode_embedding(item.embedding) for item in embeddings.data]
# 6. Store each batch in ChromaDB
collection.upsert(
ids=batch_ids,
documents=batch_chunks,
embeddings=vectors
)
print(f"Indexed batch {(i // batch_size) + 1}")
# 7. Remove outdated chunks
old_ids = [chunk_id for chunk_id in existing["ids"] if chunk_id not in kept_ids]
if old_ids:
collection.delete(ids=old_ids)
# 8. Display indexing results
print("Indexing complete!")
print("Unchanged chunks:", unchanged)
print("New or modified chunks:", len(new_chunks))
print("Removed chunks:", len(old_ids))
print("Total stored chunks:", collection.count())第 5 步:編寫 RAG 系統的協調器
將以下程式碼複製並貼上至 rag.py。這是一個完整但精簡的 RAG 實作,使用 Perplexity SDK 及 ChromaDB 的本機向量數據庫功能。
執行程式時,它會:
- 連接 Perplexity 及你新建立的 ChromaDB 數據庫。
- 檢查數據庫是否包含已建立索引的文件。
- 接收用戶提出的問題。
- 將問題傳送至 Perplexity 的嵌入模型。
- 使用 NumPy 解碼並正規化產生的嵌入向量。
- 在 ChromaDB 中搜尋三個最相似的文件區塊。
- 建構一個包含擷取區塊及用戶問題的提示。
- 透過 Perplexity 的 Agent API 將提示傳送至 GPT-5.6 Luna。
- 顯示答案,並等待下一個問題。
import base64
import numpy as np
import chromadb
from dotenv import load_dotenv
from perplexity import Perplexity
# 1. Connect to Perplexity and the existing ChromaDB database
load_dotenv()
ai = Perplexity()
db = chromadb.PersistentClient(path="./rag_database_perplexity")
collection = db.get_collection("documents")
# Convert Perplexity's encoded embeddings into numerical vectors
def decode_embedding(encoded):
vector = np.frombuffer(base64.b64decode(encoded), dtype=np.int8).astype(np.float32)
norm = np.linalg.norm(vector)
if norm == 0:
raise ValueError("Received a zero-length embedding vector.")
return (vector / norm).tolist()
# 2. Check that the database contains indexed documents
count = collection.count()
if count == 0:
raise ValueError("The database is empty. Run index.py first.")
print("Connected to existing ChromaDB database.")
print("Stored chunks:", count)
# 3. Accept questions from the user
while True:
question = input("\nAsk a question (or type exit): ")
if question.lower().strip() == "exit":
break
if not question.strip():
continue
# 4. Convert the question into a Perplexity embedding
question_response = ai.embeddings.create(
model="pplx-embed-v1-0.6b",
input=[question],
encoding_format="base64_int8"
)
question_vector = decode_embedding(question_response.data[0].embedding)
# 5. Retrieve the most similar document chunks from ChromaDB
results = collection.query(
query_embeddings=[question_vector],
n_results=min(3, count)
)
context = "\n\n".join(results["documents"][0])
# 6. Construct a prompt and send it to the fixed LLM
response = ai.responses.create(
model="openai/gpt-5.6-luna",
instructions=(
"Answer using only the supplied document excerpts. "
"Do not use external information. "
"If the answer is not present, say you don't know."
),
input=f"Document excerpts:\n{context}\n\nQuestion: {question}"
)
# 7. Display the answer
print("\nAnswer:\n", response.output_text)第 6 步:執行應用程式
在 VS Code 的 PowerShell 終端機中,輸入以下指令以嵌入 document.txt 中的內容:
.\.venv\Scripts\python.exe index.py如果文件已成功建立索引,你應該會看到類似以下的輸出:
Indexing complete!
Unchanged chunks: 0
New or modified chunks: 58
Removed chunks: 0
Total stored chunks: 58現在是時候執行主要的協調器應用程式。輸入以下指令:
.\.venv\Scripts\python.exe rag.py如果執行成功,你會看到以下內容:
Connected to existing ChromaDB database.
Stored chunks: 58
Ask a question (or type exit): 你現在可以提出問題,系統會從你建立的向量數據庫中擷取資訊來回答,例如:
Ask a question (or type exit): What animals does William Blake talk about?
Answer: The excerpts mention sheep, lambs, birds, beasts, wolves, tigers, and a lion.第 7 步:了解你構建的系統
你構建的系統包含所有 RAG 架構都具備的兩個核心管道:索引(或稱「匯入」)管道,以及擷取與生成管道。
你的基本 RAG 實作現在可以完成以下所有工作:
- 向量儲存:嵌入向量會根據你的知識檔案,儲存在本機環境的 ChromaDB 數據庫中。
- 增量索引:未變更的文件區塊不會再次嵌入,你亦可隨時更新文件。
- 語義擷取:ChromaDB 會根據向量相似度擷取區塊(由協調器發起)。
- 以擷取內容為依據生成答案:LLM 會接收擷取到的段落作為上下文,並將其融入答案中。
- 獨立的索引與查詢:你毋須重新處理知識檔案中的所有文字,即可提出問題。
- 透過 API 連接模型:所有答案均透過 Perplexity 使用 GPT-5.6 Luna 生成,而非使用在你電腦上執行的模型。
後續步驟:如何進一步擴展此實作
本教學的目的是構建一個簡單直接的基本實作,涵蓋現代系統中部分核心的 RAG 架構組件。如果你想更進一步,可以透過多種方式在此基礎上擴展。
以下是一些擴大實作範圍的構思:
- 試用 LangChain:LangChain 是最受歡迎的開源協調框架之一,內含預建組件,可用於構建能執行複雜 RAG 管道的應用程式。
- 擴充搜尋功能:將密集向量搜尋與 BM25 等詞彙(關鍵字)搜尋結合。透過 Perplexity Search API 加入網絡搜尋,亦能讓答案以最新數據為依據。
- 加入重新排序模型:整合一個重新排序模型,根據初始提示為擷取到的段落排序,並挑選最相關的段落,納入提供給 LLM 的輸入上下文中。
- 構建自適應 RAG 系統:自適應 RAG 會使用 LLM 或分類器(協調器中的一個組件),在模組化 RAG 系統中決定採用哪種擷取方法。構建這類系統是探索更進階 RAG 實作的好方法。
雖然這一切聽起來可能很複雜,但請記住,Perplexity 等主流 AI 平台可以提供進階的編程協助。這些平台非常適合個人專案和實驗,而且成本可以維持在相對較低的水平。
注意:本教學使用 GPT-5.6 Luna。模型的可用性可能會有所變更,因此你或需要更新 API 呼叫。