為什麼不整份文件存進去?
一份 40 頁的 PDF 並不是適合拿來檢索的單位。問題的答案通常只藏在某一段裡;把整份文件丟給模型,等於浪費脈絡、把關鍵那行埋起來,還要為幾千個不相關的詞元(token)付費。所以第一步是切塊(chunking):把每份文件切成小而獨立的片段——每塊從幾句到幾段不等——讓它們各自都能被搜尋與檢索。
好的切塊會尊重文件本身的結構。要沿著標題、段落或清單項目來切,而不是死板地每 N 個字就切一刀——一塊切到句子中間,既難向量化又難閱讀。對於結構性很強的資料(表格、程式碼、問答配對),就讓每一筆邏輯記錄保持完整。
塊的大小與重疊
大小要拿捏。太大,一塊會混進好幾個主題,搜尋訊號就糊掉了;太小,一塊又失去讓它有意義的脈絡。常見的起點是每塊幾百個詞元。請拿你自己的文件去調,別盲信預設值。
重疊(overlap)能解決邊界問題:讓相鄰的兩塊在交界處共用一點文字——例如把前一塊的最後一句,重複放到下一塊的開頭。沒有重疊的話,剛好橫跨邊界的一個事實會被攔腰切斷,可能永遠無法被完整檢索到。適度的重疊(通常是一塊的 10–20%)是很划算的保險。
把塊轉成向量
為了用語意而非逐字相符來搜尋,每一塊都會通過一個嵌入模型(embedding model),把文字對映成一串數字——也就是嵌入(embedding)——也就是高維語意向量空間(semantic vector space)裡的一個點。關鍵性質是:意思相近的塊會落在彼此附近,就算它們沒有共用任何字也一樣。「取消我的方案」和「我要怎麼終止訂閱」最後會變成鄰居。
文字被切分为 token,映射为 token id,再转换为嵌入向量。
事先把每一塊都向量化一次,並把產生的向量存起來。查詢時,再用同一個模型把問題向量化,然後比對。這整套做法——拿問題向量去比對各塊的向量——就是基於嵌入的檢索(embedding-based retrieval),也就是語意搜尋背後的引擎。
向量倉儲
把問題逐一去比對上百萬個塊向量會太慢。向量倉儲(vector store)——底層是一個向量資料庫——會建立索引,用近似最近鄰搜尋在幾毫秒內找出最接近的向量。你把塊(連同文字與中介資料)加進去,之後再問:給我離這個查詢向量最近的 k 塊。
向量库按余弦相似度对文本块排序——用问题向量 q 比对每个文本块向量 d。
# One-time indexing of the knowledge base
for doc in documents:
for chunk in split(doc, size=400, overlap=60): # chunk + overlap
vec = embed(chunk.text) # same model used at query time
store.add(id=chunk.id,
vector=vec,
text=chunk.text,
meta={'source': doc.title, 'page': chunk.page})
# At query time
q_vec = embed(user_question)
hits = store.search(q_vec, k=5) # 5 nearest chunks, by meaning