Applied AI School
v0 · 規劃中
Anthropic

完整 RAG flow

從 chunk 到 generate 的 5 步驟 pipeline——一個極簡 in-memory 範例,順便講 vector DB 怎麼選。

TL;DR

  • 5 步:chunk → embed → store(preprocess 階段)→ retrieve → augment → generate(query 階段)
  • top-k 通常 3–10;太多會 dilute context、太少會漏答案
  • Retrieval 的 query 不一定要用 user 原文——可以先讓 model 改寫成 search query

一個情境:把上一篇兜起來

延續 FAQ bot 例子。我們有:

  • 一份 report.md,按 ## header 切成 12 個 chunk
  • Voyage embedding API
  • 一個 user 問題:「軟體工程部去年做了什麼?

目標:retrieve 最相關的 2 個 chunk,丟給 Claude 回答。

5 個步驟

RAG 的兩階段——左邊一次性 preprocess,右邊每次 query 都跑

前 3 步是一次性的(除非文件變動再重跑);後 3 步每次 user 問問題都跑

極簡 in-memory 實作

不用真的 vector DB,先用 numpy 把概念跑出來:

import numpy as np
import voyageai

vo = voyageai.Client()

def embed(texts, input_type):
    return vo.embed(texts, model="voyage-3-large",
                    input_type=input_type).embeddings

# Step 1-2: chunk + embed (preprocess)
chunks = chunk_by_section(open("report.md").read())
vectors = np.array(embed(chunks, input_type="document"))

# Step 3: 「store」就是把 vectors 放在記憶體裡(in-memory 版)

# Step 4: embed user query
q = "What did the software engineering dept do last year?"
qv = np.array(embed([q], input_type="query")[0])

# Step 5: cosine similarity → top-k
sims = vectors @ qv  # 假設都已經 normalize 過,dot product = cosine
top_k = np.argsort(-sims)[:3]
retrieved = [chunks[i] for i in top_k]

關鍵點:

  • vectors @ qv 算的是 cosine similarity(因為 Voyage 預設 normalize)
  • argsort(-sims)[:3] 取相似度最高的 3 個 chunk
  • retrieved 就是要塞進 prompt 的 context

Step 6:augment + generate

把 retrieved chunks 包進 prompt。包法很重要:用 XML tag 把 context 跟 user query 分開:

context = "\n\n".join(f"<doc>\n{c}\n</doc>" for c in retrieved)

prompt = f"""根據下面文件回答 user 的問題。如果文件沒有答案,明確說不知道。

{context}

User 問題:{q}"""

res = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": prompt}],
)
print(res.content[0].text)

<doc>...</doc> 不是必要語法,但 Claude 對 XML tag 特別熟(pretraining 看了很多)。把多段 context 用 tag 框起來,Claude 比較不會把「指令」跟「資料」混淆。

Cosine similarity 在做什麼

Vector DB 比對 query vector 跟所有 chunk vector 的「角度」:

角度cosine similarity意義
1.0兩段文字幾乎同義
90°0.0完全無關
180°-1.0反義(很少見)

文件中常看到「cosine distance」——就是 1 - cosine similarity。distance 越小越相似(很多 vector DB API 用這個慣例)。

Top-k 怎麼選

場景k 建議
問題明確、答案集中(FAQ、查 incident ID)3
一般文件問答5
探索式、總結式問題8–10
法律 / 醫療等高 recall 場景10–20,後面再 rerank

k 不是越大越好:每個多塞一段就多一份 input token,而且不相關的 chunk 會干擾 model 判斷。先用 k=5 起手,看 retrieval 準確率再調。

Vector DB:production 怎麼選

In-memory numpy 跑 prototype 很方便,但 chunk 上萬就要正式的 vector DB:

DB適合什麼特性
pgvector(Postgres extension)已經在用 Postgres、不想多開服務跟現有 RDB 同一份、SQL filter + vector 一起查
Qdrant中型 production、self-host 或 cloudopen source、有強 filter、API 乾淨
Pinecone不想自己管 infra、量很大全 managed、scale 容易、貴
Weaviate / Milvus / Chroma各有特色看團隊偏好

選擇 rule

  • 已經用 Postgres → pgvector 八成夠
  • 量超過 10M vectors / 要 multi-tenant → Pinecone 或 Qdrant cloud
  • 一切重 self-host → Qdrant

不要花太多時間糾結,底層搬遷不難(API 都是 add(vec, meta) + search(qv, k))。先用方便的跑通整條 pipeline,再看瓶頸換。

預告:retrieval 還可以怎麼進化

5 步流程跑起來了,但 production 會撞到的事:

  1. 純 vector 對精確關鍵字(API name、錯誤碼、product SKU)很爛 — 下一篇講 hybrid 怎麼解
  2. top-k 順序不夠準 — reranker(Voyage rerank-2 / Cohere rerank)
  3. 用 Claude 回答時想知道引用了哪段 — Section 6 的 citations 跟 RAG 是一對親戚,做完這段一定要去看
  4. 每次都重 embed 整本書浪費錢 — preprocessing 結果存好就好

接下來

下一篇處理 #1:multi-index pipeline——semantic search 補上 BM25 lexical search,再用 reranker 把結果合併。production 的 RAG 八成長這樣。