API 開發

embedding 與向量檢索是怎麼運作的?看懂 RAG 的搜尋心臟

embedding 是把一段文字的語意轉成一長串數字的表示法。RAG 把每個切塊與使用者的提問都轉成 embedding 存進向量資料庫,再用 cosine similarity 比較哪些切塊跟提問的語意最接近。整個檢索因此變成一個可以計算的數學問題。
embedding 與向量檢索是怎麼運作的?看懂 RAG 的搜尋心臟:文章重點卡

embedding 與向量檢索是怎麼運作的?看懂 RAG 的搜尋心臟

文件切好塊之後,下一個問題來了:使用者提問時,要怎麼從一堆切塊裡找出最相關的那幾塊?這本質上是個搜尋問題。

RAG 最常用的答案是語意搜尋(semantic search):不比對字面,而是比對意思。而讓意思可以被比對的關鍵技術,就是這篇的主角 embedding。

你將學到什麼

embedding 是什麼

把文字的語意變成一串數字的表示法。

動手產生 embedding

用 VoyageAI 的 API 幾行程式就能做到。

cosine similarity

向量資料庫怎麼用角度判斷誰跟誰相似。

完整檢索流程

從切塊、存庫到提問檢索的六個步驟。

找相關段落,其實是個搜尋問題

上一篇我們把文件切成了塊,現在得在使用者提問時,從所有切塊裡挑出跟問題相關的。傳統的關鍵字搜尋比對的是「有沒有出現同樣的字」,而語意搜尋走的是另一條路:把提問與每個切塊都轉成 embedding,比較它們在意思上的接近程度。

兩條路各有擅場,之後談檢索品質時會再回來比較;這篇先把語意搜尋這條路走通。

embedding:把語意變成一串數字

embedding 是一段文字所含語意的數值表示。你把文字餵進 embedding 模型,它吐回一長串數字,每個數字介於負 1 到正 1 之間,各自代表輸入文字的某種特質或特徵。

這裡有個重要的但書:我們並不知道每個數字精確代表什麼。你可以想像某個數字代表「這段文字有多開心」或「有多常談到海洋」,但這只是幫助理解的比喻。每個維度的實際意義是模型在訓練過程中自己學出來的,人類無法直接解讀。

動手產生 embedding

Anthropic 目前不提供 embedding 生成,這裡選的供應商是 VoyageAI:註冊帳號、拿到 API key(入門免費),放進環境變數,然後安裝套件、寫一個產生 embedding 的函式:

# .env
VOYAGE_API_KEY="your_key_here"

# pip install voyageai
from dotenv import load_dotenv
import voyageai

load_dotenv()
client = voyageai.Client()

def generate_embedding(text, model="voyage-3-large", input_type="query"):
    result = client.embed([text], model=model, input_type=input_type)
    return result.embeddings[0]

對任何一段文字呼叫它,就會拿回一串浮點數。產生 embedding 本身很快也很簡單,真正的功夫在於接下來怎麼比較這些數字。

用一個想像的模型,看懂整個流程

真實的 embedding 有成百上千個維度,很難視覺化。這裡換一個好懂的想像:假設有個完美的 embedding 模型,永遠只回傳兩個數字,而且我們知道第一個數字代表「談醫療的程度」,第二個代表「談軟體工程的程度」。

拿兩個切塊來看。醫學研究的段落可能得到 [0.97, 0.34]:非常醫療,但因為出現了「bug」這個字而帶點軟體味。軟體工程的段落得到 [0.30, 0.97]:重度軟體,但「infection vectors」這種詞又帶點醫療的影子。embedding API 通常會做正規化,把每個向量縮放成長度 1.0,於是變成 [0.944, 0.331] 與 [0.295, 0.955],可以畫在單位圓上。

接著使用者提問:「我想了解這家公司,特別是軟體工程部門今年做了什麼?」這句話跑過同一個 embedding 模型,得到類似 [0.1, 0.89] 的結果:醫療分數低、軟體分數高,正規化後是 [0.112, 0.993]。

醫療相關度軟體相關度醫療段落軟體段落使用者提問正規化後的向量都落在單位圓上
提問的向量跟軟體段落的向量夾角很小,代表語意相近;跟醫療段落的夾角大,語意就遠。

cosine similarity:向量資料庫怎麼比相似

切塊的 embedding 會存進向量資料庫:一種專門儲存、比較、搜尋這種長串數字的資料庫。到這裡為止都是可以事先做完的前處理,接下來就等使用者提問。

提問進來、轉成 embedding 之後,向量資料庫用 cosine similarity(餘弦相似度)找最接近的向量:計算兩個向量夾角的餘弦值。數值越接近 1 代表越相似,接近負 1 代表非常不同,0 則代表垂直、沒什麼關係。在剛才的例子裡,提問與軟體段落的相似度是 0.983,跟醫療段落只有 0.398,高下立判。

很多向量資料庫的文件會改用 cosine distance(餘弦距離),也就是 1 減掉 cosine similarity:距離越接近 0 越相似,數值越大越不相關。同一件事,換個方向表達而已。

把流程串起來,並且記得存原文

整條實作流程就是五步:切塊、為每塊產生 embedding、存進向量庫、把使用者提問轉成 embedding、搜尋最相近的切塊。程式骨架長這樣:

chunks = chunk_by_section(text)
embeddings = generate_embedding(chunks)

store = VectorIndex()
for embedding, chunk in zip(embeddings, chunks):
    store.add_vector(embedding, {"content": chunk})

user_embedding = generate_embedding(
    "What did the software engineering dept do last year?"
)
results = store.search(user_embedding, 2)
存 embedding 時務必連原文一起存搜尋的時候,拿回一串數字沒有用,你需要的是能放進 prompt 的原文。所以每筆向量都要連著原始切塊的文字(或至少一個能找回原文的參照)一起存進資料庫。

拿範例文件實測,問軟體工程部門做了什麼,搜尋回傳軟體工程章節(距離 0.71,最接近)與方法論章節(距離 0.72,次接近)。距離越小代表越相關,檢索結果合情合理。

說到底,RAG 的核心就是:把文字變成數字、把數字存好,再用數學上的相似度找回相關的內容。不過語意搜尋不是萬能的,有些情境它會失手。下一篇我們就來補上它的盲點:關鍵字搜尋與混合檢索。

參考出處本文取材自 Anthropic 官方 Claude Academy 免費課程「Building with the Claude API」,由酒Ann 消化後以自己的視角重新編寫。想看英文原版課程,可到 Claude Academy 修習。

延伸學習

把這篇文章分享給需要的人FacebookLINEThreadsX

常見問答

embedding 裡的每個數字代表什麼意思?
每個數字可以想成輸入文字某種特質的分數,但實際上每個維度的意義是模型訓練時自己學出來的,人類無法直接解讀。
為什麼要另外用 VoyageAI?
課程說明 Anthropic 目前不提供 embedding 生成服務,推薦的供應商是 VoyageAI:另外註冊帳號、取得 API key 就能開始用。
cosine similarity 的數值怎麼解讀?
範圍從負 1 到 1:越接近 1 代表語意越相似,接近負 1 代表非常不同,0 代表兩者垂直、沒什麼關係。
cosine distance 又是什麼?
就是 1 減掉 cosine similarity。距離越接近 0 代表越相似,數值越大代表越不相關,在很多向量資料庫的文件裡會看到這種寫法。