Applied AI School
v0 · 規劃中
Anthropic

Embeddings 與 chunking

把文字變成可比較的座標,把長文切成適合 retrieve 的單位——RAG 的兩塊基石。

TL;DR

  • Embedding = 「文字的座標」,意義近的座標就近——這是 semantic search 的數學基礎
  • Chunking 不是切均等大小——句子 / 段落 / semantic boundary 通常更好
  • Chunk 太小:失去 context;太大:embedding 不準。實務 300–800 token 起手

一個情境:相同關鍵字 ≠ 相關

User 問:「員工的 bug 修了多少?

文件裡兩段都有 "bug" 這個字:

  • A 段 — 醫療研究:「今年我們發現一種新的病毒 'bug',XDR-47……」
  • B 段 — 軟體工程:「本季 engineering 修了 47 個 P1 issue……」(沒有 "bug" 字)

純關鍵字搜尋會抽 A 段(有 "bug");但人類一看就知道 B 段才是答案。這就是為什麼需要 embedding:它比對的是「意思」不是「字面」。

Embedding 是什麼

把一段文字餵給 embedding model,會吐出一個數字 list(典型 512 / 1024 / 1536 維)。每個數字大致在 -1 到 +1 之間,整個 vector 代表這段文字的「語意座標」

先裝 SDK,去 voyageai.com 拿一把 API key 設成 VOYAGE_API_KEY 環境變數:

pip install voyageai
import voyageai

vo = voyageai.Client()  # 讀 VOYAGE_API_KEY

def embed(text: str, input_type: str = "document"):
    res = vo.embed([text], model="voyage-3-large", input_type=input_type)
    return res.embeddings[0]  # list[float],長度 1024

vec = embed("今年軟體工程部修了 47 個 bug")
# [0.013, -0.041, 0.082, ...]  共 1024 個數字

關鍵性質:意義相近的兩段文字,embedding 之間的角度(cosine)會很小。這讓我們可以「用數學算相似度」而不是「字串比對」。

每個 dimension 代表什麼?沒人知道,是訓練出來的,不可解釋。你可以想像第一維代表「這段話多醫療」、第二維「多技術」之類的(只是助記用,實際不是)。

為什麼要 chunk

Embedding model 有 input 上限(Voyage voyage-3-large 是 32K token),但更重要的是:整份文件 embed 成一個 vector 沒意義。你問「軟體工程部做了什麼」,整本年報的 vector 跟你的 query 算 cosine 一定不準。

所以要切 chunk,每個 chunk 各自 embed、各自存。chunk 才是 retrieve 的最小單位

三種 chunking 策略

策略怎麼切優點缺點
Size-based固定字元數(例:500 字一段,overlap 50)任何文件都能用、好實作切到句子中間、失去結構
Structure-based按 markdown header / paragraph 切chunk 邊界自然需要文件本身有結構(PDF、純文字不適用)
Semantic切句後用 NLP 判斷句子相關性,把相關的句子合併品質最高慢、實作複雜

實務取捨:

  • Markdown / 規範文件 / FAQ:structure-based 最自然
  • PDF 抽出來的純文字 / 客服紀錄:sentence-based 或 size-based + overlap
  • 不知道用哪個:size-based 800 chars + 100 overlap 起手——production 八成都這樣
import re

def chunk_by_section(text: str) -> list[str]:
    # markdown 用 ## 切
    return re.split(r"\n## ", text)

def chunk_by_size(text: str, size=800, overlap=100) -> list[str]:
    chunks, i = [], 0
    while i < len(text):
        chunks.append(text[i:i + size])
        i += size - overlap
    return chunks

Chunk 大小的取捨

Chunk 太小(< 200 token)Chunk 太大(> 1500 token)
失去周邊 contextembedding 被「平均化」,特定主題訊號被稀釋
一個概念被切兩段retrieve 回來會塞爆 prompt
retrieve 出 5 個都只看到半句話top-k 的「k」實質變小

起手 rule of thumb:300–800 token 的 chunk + 50–100 token 的 overlap。Overlap 是為了避免「答案剛好被切在邊界」。

# 估 token 數的偷吃步:英文約 4 字元 / token,中文約 1.5 字元 / token
# size=800 chars 的英文 chunk ≈ 200 token;中文 ≈ 530 token

一個常被忽略的細節:input_type

很多 embedding API(Voyage、Cohere)允許指定 input_type

  • document:embed 你存進 DB 的 chunk
  • query:embed user 的問題

兩邊用同一個 model,但內部會用稍微不同的方式對待——這讓「短 query」跟「長 chunk」的 vector 在同一個空間更可比。漏掉這個 flag 不會壞,但 retrieval 品質會差一截。

chunk_vec = embed(chunk, input_type="document")  # 存進 DB 的
query_vec = embed(user_question, input_type="query")  # 來查的

接下來

有了 embedding 跟 chunking,下一篇把整個 RAG flow 串起來——chunk → embed → store → retrieve → augment → generate,5 個步驟一個極簡 in-memory 範例 + 怎麼選 vector DB。