前幾天分別談了 Embedding 與向量資料庫的選型,今天要把兩者串接起來,實際把 Day 9 產出的 chunk 資料,轉換成向量並寫入資料庫,完成 Indexing 階段的最後一哩路。
原始文件 → 前處理 Pipeline(Day 5-9) → chunk 清單
│
▼
逐筆呼叫 Embedding API
│
▼
寫入向量資料庫(含 content + embedding + metadata)
│
▼
建立索引,等待查詢
import psycopg2
from psycopg2.extras import execute_values
def insert_chunks_to_db(chunks, conn):
"""
chunks: [{"content": ..., "embedding": [...], "metadata": {...}}, ...]
"""
cur = conn.cursor()
data = [
(c["content"], c["embedding"], psycopg2.extras.Json(c["metadata"]))
for c in chunks
]
execute_values(
cur,
"INSERT INTO chunks (content, embedding, metadata) VALUES%s",
data,
template="(%s,%s,%s)"
)
conn.commit()
cur.close()
批次寫入(execute_values)相較於逐筆 INSERT,可以大幅減少資料庫往返次數,加快寫入速度。
def build_index(doc_dir, conn):
all_chunks = []
for file_path in doc_dir.glob("**/*"):
if file_path.suffix in [".pdf", ".html", ".md"]:
chunks = preprocess_pipeline(file_path) # Day 9 的 Pipeline
all_chunks.extend(chunks)
# 批次做 Embedding(可依 API 限制調整批次大小)
batch_size = 50
for i in range(0, len(all_chunks), batch_size):
batch = all_chunks[i:i+batch_size]
texts = [c["content"] for c in batch]
embeddings = get_embeddings_batch(texts) # 批次呼叫 Embedding API
for c, emb in zip(batch, embeddings):
c["embedding"] = emb
insert_chunks_to_db(all_chunks, conn)
print(f"共處理{len(all_chunks)} 個 chunk,已寫入資料庫")
-- 檢查資料筆數
SELECT COUNT(*) FROM chunks;
-- 檢查是否有 embedding 為 NULL 的異常資料
SELECT COUNT(*) FROM chunks WHERE embedding IS NULL;
-- 簡單測試相似度搜尋是否能運作
SELECT content FROM chunks
ORDER BY embedding <=> (SELECT embedding FROM chunks LIMIT 1)
LIMIT 5;
到這裡,Indexing 階段的完整流程——文件前處理、Chunking、Embedding、寫入向量資料庫——已經串接完成。這代表我們的知識庫已經「可以被查詢」了。明天開始,我們要進入 Retrieval 階段,先從最基礎的相似度搜尋原理談起。