一句話理解
RAG = 把你的文件塞進向量資料庫,讓 AI 查詢後再回答,解決 LLM「不知道你公司內部資料」的問題。
RAG 流程
[文件] → 切片 → 向量化 → 存入向量DB(建置期)
[問題] → 向量化 → 相似度搜尋 → 取得相關片段 → 傳給 LLM → 回答(查詢期)
環境安裝
pip install langchain langchain-openai langchain-community chromadb
pip install pypdf # 讀 PDF
建立向量資料庫
from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
# 1. 載入文件
loader = PyPDFLoader("docs/manual.pdf")
docs = loader.load()
# 純文字也可以
loader = TextLoader("docs/notes.txt", encoding="utf-8")
docs = loader.load()
# 2. 切片(chunk)
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每塊最多 1000 字
chunk_overlap=200, # 前後重疊 200 字(避免截斷重要內容)
)
chunks = splitter.split_documents(docs)
print(f"切成 {len(chunks)} 塊")
# 3. 向量化並存入 ChromaDB
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # 存到硬碟
)
查詢向量資料庫
# 載入已存在的向量DB
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=OpenAIEmbeddings()
)
# 相似度搜尋(回傳最相關的 3 塊)
docs = vectorstore.similarity_search("JWT 怎麼設定?", k=3)
for doc in docs:
print(doc.page_content)
print(doc.metadata) # 包含來源檔名、頁碼等
RAG Chain(完整問答)
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 建立 retriever
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# Prompt(把搜尋結果塞進去)
prompt = ChatPromptTemplate.from_messages([
("system", """你是一個技術助理,根據以下資料回答問題。
如果資料中找不到答案,請說「我找不到相關資訊」,不要自己猜。
參考資料:
{context}"""),
("human", "{question}")
])
llm = ChatOpenAI(model="gpt-4o-mini")
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# RAG chain
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 執行
answer = rag_chain.invoke("JWT 的 secret key 怎麼設定?")
print(answer)
多種 Document Loader
# PDF
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("file.pdf")
# 純文字
from langchain_community.document_loaders import TextLoader
loader = TextLoader("file.txt")
# 整個資料夾(遞迴讀所有 .txt)
from langchain_community.document_loaders import DirectoryLoader
loader = DirectoryLoader("./docs", glob="**/*.txt")
# 網頁
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://example.com/docs")
# Notion
from langchain_community.document_loaders import NotionDBLoader
loader = NotionDBLoader(integration_token="...", database_id="...")
docs = loader.load()
向量資料庫比較
| 向量DB | 特色 | 適合場景 |
|---|
| Chroma | 輕量、本地、免費 | 開發測試 |
| FAISS | Meta 出品、純 in-memory | 本地快速搜尋 |
| Pinecone | 雲端託管、scale 強 | 生產環境 |
| Weaviate | 支援 hybrid search | 混合搜尋需求 |
| pgvector | PostgreSQL 擴充 | 已有 PG 環境 |
常見錯誤
| 錯誤 | 原因 | 解法 |
|---|
| 回答胡說八道 | chunk 太大,搜尋不精準 | 縮小 chunk_size(500-800) |
| 截斷重要內容 | chunk_overlap 太小 | 加大 chunk_overlap(150-200) |
| 搜尋結果不相關 | k 值太小 | 試試 k=5 |
| Embedding 費用 | 每次都重新向量化 | 存 persist_directory 快取 |
相關筆記