一句話理解

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輕量、本地、免費開發測試
FAISSMeta 出品、純 in-memory本地快速搜尋
Pinecone雲端託管、scale 強生產環境
Weaviate支援 hybrid search混合搜尋需求
pgvectorPostgreSQL 擴充已有 PG 環境

常見錯誤

錯誤原因解法
回答胡說八道chunk 太大,搜尋不精準縮小 chunk_size(500-800)
截斷重要內容chunk_overlap 太小加大 chunk_overlap(150-200)
搜尋結果不相關k 值太小試試 k=5
Embedding 費用每次都重新向量化persist_directory 快取

相關筆記