Ian Chou's Blog

從零打造 Entity Embedding Graph RAG:一門實戰迷你課的完整藍圖

從零打造 Entity Embedding Graph RAG:一門實戰迷你課的完整藍圖

這篇文章是我規劃「Entity Embedding Graph RAG」迷你課程的完整思考記錄。核心概念只有一句話:用語意相似度取代關鍵字匹配來建構圖的邊。用 LanceDB + NetworkX 幾十行 Python 就能落地,學完直接變 portfolio。

這門課要解決什麼問題?

上一篇中,我得出了一個結論:200 nodes 的個人知識圖譜不需要升級到 Memgraph 或 HelixDB,真正值得做的是 Entity Embedding Graph——用 embedding 相似度自動建構 skill 之間的語意邊,取代手動維護的 skill-graph.json

這門課就是把這個想法從概念變成可落地的教學內容。

目標學員

學完之後

學員會擁有一個完整的 Graph-RAG Career Assistant:輸入 career 相關問題,系統透過 semantic skill graph + chunk retrieval 給出有結構的回答。Repo 直接 pin 到 GitHub profile 當 portfolio。


為什麼是 Entity Embedding Graph?

在切入課綱之前,先回顧三種 Semantic Graph 的實作方式,解釋為什麼選 B:

三條路線

比較面向 A: Chunk kNN B: Entity Embedding C: HelixDB / LightRAG
核心思路 chunk 之間靠向量相似度連邊 skill entity 靠向量相似度連邊 vector + graph 融合在同一 DB
新程式碼行數 ~25 行 ~40 行 大量
需要新套件
適合 200 筆 ❌ overkill

為什麼 B 最適合教學?

方式 A(Chunk kNN) 在單一 Q&A 場景表現好,但對「Kubernetes 需要什麼?」這種多 skill 比較問題只能靠 chunk 裡碰巧提到的內容,結果不穩定。

方式 B(Entity Embedding) 直接在 skill 層級建圖,捕捉 skill 之間的結構化語意關係。embed("Kubernetes") · embed("Docker") = 0.87——這種邊不是人工定義的,是語意空間自動發現的。

效果差異一目了然:

情境 方式 A (chunk) 方式 B (entity)
單 chunk Q&A 中等
多 skill 比較 中等 優秀
整體 RAG 準確率提升 +10–20% +30–90%
Career knowledge graph 快但有雜訊 精準且穩定

方式 C 概念最美,但 200 nodes 殺雞用牛刀。課程不應該讓學員花 80% 時間搞環境。

最佳策略:B 為主幹,A 為輔助

這就是 HybridRAG——entity 給結構,chunk 給內容。


課程大綱:6 章 × 2–4 小時

每一章都能對應到一個實際的 .py.ipynb 產出物。目標是「做中學」,不是講空泛的 embedding 理論。

Ch1:為什麼需要 Skill Entity Graph?(15 min)

目標:讓學員 10 分鐘內懂「為什麼用 Entity Embedding Graph,而不是只做 vector search 或手寫 skill-graph.json」。

內容

對應檔案notebooks/01_motivation.ipynb

作業:跑 demo,自己觀察兩種方式的回答品質差異。


Ch2:Embedding 夠用的基礎(20 min)

目標:只講「夠用」的 embedding 知識,用實際會用到的模型當例子。

內容

實作小段

from sentence_transformers import SentenceTransformer
import numpy as np

model = SentenceTransformer("BAAI/bge-m3")

skills = ["Python", "FastAPI", "Kubernetes", "Docker", "Photoshop"]
embeddings = model.encode(skills)

# Similarity matrix — 讓學員「看見」語意距離
for i, s1 in enumerate(skills):
    for j, s2 in enumerate(skills):
        sim = np.dot(embeddings[i], embeddings[j]) / (
            np.linalg.norm(embeddings[i]) * np.linalg.norm(embeddings[j])
        )
        if i < j:
            print(f"  {s1}{s2}: {sim:.3f}")

學員會看到 Python–FastAPI 相似度遠高於 Python–Photoshop,直覺理解 embedding 的意義。

對應檔案notebooks/02_embedding_basics.ipynb

作業:embed 自己的技能清單,觀察哪些技能 cluster 在一起。


Ch3:Skill List → Entity Embedding Table(25 min)

目標:從「一串技能名稱」變成「可查詢的 LanceDB 向量表」,為建圖打底。

內容

# embed_skills.py — 核心邏輯
import lancedb
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-m3")

def embed_and_store(skills_yaml_path, db_path="data/skills.lance"):
    skills = load_yaml(skills_yaml_path)
    
    texts = [f"{s['name']}: {s.get('description', s['name'])}" for s in skills]
    vectors = model.encode(texts)
    
    db = lancedb.connect(db_path)
    table = db.create_table("skills", [
        {"id": i, "name": s["name"], "category": s.get("category", ""),
         "text": texts[i], "vector": vec.tolist()}
        for i, (s, vec) in enumerate(zip(skills, vectors))
    ], mode="overwrite")
    
    return table

對應檔案src/embed_skills.py

CLI 用法python src/embed_skills.py --input data/skills.yaml --out data/skills.lance

作業:加 10 個新 skill 並重新 embed,觀察 table 變化。


Ch4:用相似度自動建 Skill Graph — 核心章(30 min)

目標:完整實作方式 B,從向量表 → NetworkX skill graph。

本章設計為三步:

Step 1:kNN 查詢

# build_graph.py — 核心邏輯
import networkx as nx

def build_knn_graph(table, top_k=5, threshold=0.2):
    """用 embedding 相似度自動建立 skill 之間的語意圖。"""
    skills = table.to_pandas()
    graph = nx.Graph()
    
    for _, row in skills.iterrows():
        graph.add_node(row["name"], category=row.get("category", ""))
    
    for _, row in skills.iterrows():
        results = table.search(row["vector"]).limit(top_k + 1).to_list()
        for r in results:
            if r["name"] != row["name"] and r["_distance"] < threshold:
                similarity = 1 - r["_distance"]
                graph.add_edge(row["name"], r["name"], weight=similarity)
    
    return graph

Step 2:邊權重與圖類型

Step 3:Hyperparameter 調整策略

# 調參工具
import matplotlib.pyplot as plt

distances = []
for _, row in skills.iterrows():
    results = table.search(row["vector"]).limit(20).to_list()
    distances.extend([r["_distance"] for r in results if r["name"] != row["name"]])

plt.hist(distances, bins=50)
plt.axvline(x=0.2, color='r', linestyle='--', label='threshold=0.2')
plt.xlabel("Distance")
plt.ylabel("Count")
plt.title("Skill Embedding Distance Distribution")
plt.legend()
plt.show()

對應檔案src/build_graph.py + notebooks/03_hyperparam_tuning.ipynb

作業:給學員一份 100–200 筆技能清單,調 top_k / threshold,觀察:


Ch5:接回 Hybrid RAG Pipeline(35 min)

目標:把 entity graph 正式接入 retrieval pipeline。這是最核心的章節,需要整合 skill graph + chunk layer 形成完整的 HybridRAG。

為什麼要加 Chunk Layer?

純 Entity Graph 有兩個痛點:

  1. 結構好但內容少:Entity graph 抓到 "Kubernetes → Docker",但沒有「具體怎麼學 Docker 的 chunk」
  2. Chunk 單獨用太碎:純 vector search 抓 chunk 會抓到「提到 Kubernetes 但不相關」的雜訊

解法:Chunk metadata 加 skill labels,讓 chunk 變成 graph-aware。

預處理:Chunk Enrichment

# chunk_enrich.py — 讓 chunk 知道自己屬於哪些 skills
def enrich_chunks(chunk_table, skill_table, top_k=3):
    """為每個 chunk 自動標註相關的 skills(用 embedding 相似度)。"""
    chunks = chunk_table.to_pandas()
    
    for idx, chunk in chunks.iterrows():
        # 找這個 chunk 最接近的 skills
        related = skill_table.search(chunk["vector"]).limit(top_k).to_list()
        chunk_table.update(
            where=f"id = {chunk['id']}",
            values={"linked_skills": [r["name"] for r in related]}
        )

設計決策:這裡用 embedding 相似度做 chunk→skill 標註。對於縮寫或別名(如 k8s / Kubernetes、Go / Golang),embedding model 本身就能處理大部分情況。如果遇到極端 case,可在建表前做一輪資料清理,或引入極輕量的 LLM 輔助萃取——這是「Context-first」設計哲學,在寫入階段就把語意與結構整理好。

查詢時:Hybrid Retrieval

# rag_pipeline.py — 完整 hybrid retrieval 流程
import networkx as nx

def hybrid_retrieve(query, query_embedding, skill_table, chunk_table, skill_graph, 
                    skill_top_k=5, graph_hops=2, chunk_top_k=10):
    """
    Query → Skill Graph Expansion → Graph-guided Chunk Retrieval
    
    1. 在 skill entity table 找 seed skills
    2. 沿 skill graph 走 N hop,擴展到相關 skills
    3. 用這組 skills 過濾 chunk table,做二次檢索
    """
    # Step 1: 找 seed skills(entity-level retrieval)
    seed_skills = skill_table.search(query_embedding).limit(skill_top_k).to_list()
    seed_names = {r["name"] for r in seed_skills}
    
    # Step 2: Graph expansion — 沿語意圖走 N hop
    expanded_skills = set()
    for skill_name in seed_names:
        if skill_name in skill_graph:
            neighbors = nx.single_source_shortest_path_length(
                skill_graph, skill_name, cutoff=graph_hops
            )
            expanded_skills.update(neighbors.keys())
    
    all_skills = seed_names | expanded_skills
    
    # Step 3: Graph-guided chunk retrieval
    # 只在帶有相關 skill labels 的 chunks 裡搜
    chunks = chunk_table.search(query_embedding).limit(chunk_top_k * 3).to_list()
    
    # 過濾:只保留 linked_skills 有交集的 chunks
    filtered = [
        c for c in chunks
        if set(c.get("linked_skills", [])) & all_skills
    ][:chunk_top_k]
    
    return {
        "seed_skills": list(seed_names),
        "expanded_skills": list(expanded_skills - seed_names),
        "chunks": filtered
    }

Aha Moment 範例設計

這是讓學員一眼看出 Semantic Graph 威力的關鍵對比——展示 「跨越未知(Unknown Unknowns)」 的能力。

情境:知識庫裡有三篇文章:

Query:「我平常用 Python 寫 FastAPI,現在想在 JavaScript/Edge 生態找一個類似的輕量、高效能 API 框架,有什麼學習資源?」

方法 檢索結果 解釋
❌ Vector-only Chunk A + Chunk B 靠字面相似度,找不到 Hono
✅ Hybrid RAG Chunk C 🎯 Skill graph:FastAPI → 輕量 Web 框架 → Hono;Graph 搭了一座語意的橋

學員看到這個對比的瞬間就會明白:Graph 不是取代向量搜尋,而是向量搜尋的「導航圖」

三種方式對決

在 notebook 中用同一個 query 跑三種 retrieval,讓學員自己觀察:

方法 Context 品質 缺點
Vector-only chunk 快但雜 可能抓到字面相關但不相關的 chunk
Skill graph only 結構好但內容少 只有 skill name,沒有學習細節
Hybrid(Skill + Chunk) 最佳 零額外依賴

對應檔案src/chunk_enrich.py + src/rag_pipeline.py + notebooks/05_rag_comparison.ipynb

作業:測試 3 個跨領域 career query,比較 Hybrid 效果。調 cutoff=1 vs cutoff=2 觀察擴展範圍。


Ch6:Bonus — Learning Path 推薦 + 延伸(20 min)

目標:展示 skill graph 的進階應用,讓學員有「原來還能這樣用」的驚喜。

內容(挑 1–2 個):

# recommend_path.py — 簡單的 learning path 推薦
def recommend_next_skills(skill_graph, current_skills, top_n=5):
    """根據你已有的技能,推薦下一步該學什麼。"""
    candidates = {}
    
    for skill in current_skills:
        if skill not in skill_graph:
            continue
        for neighbor in skill_graph.neighbors(skill):
            if neighbor not in current_skills:
                weight = skill_graph[skill][neighbor].get("weight", 0.5)
                candidates[neighbor] = candidates.get(neighbor, 0) + weight
    
    # 按權重排序,相似度越高 = 越值得學
    ranked = sorted(candidates.items(), key=lambda x: -x[1])
    return ranked[:top_n]

# 用法:
# recommend_next_skills(graph, ["Python", "FastAPI", "Docker"])
# → [("Kubernetes", 1.74), ("Flask", 1.2), ("Celery", 0.95), ...]

對應檔案:notebooks 全 + src/recommend_path.py(可選)

作業:fork repo,加一個自己想要的功能。


Repo 結構:課程導向設計

entity-embedding-graph-rag/
├── README.md                    # 總覽 + 快速啟動
├── requirements.txt             # pip install -r
├── Dockerfile                   # 可選:一鍵 Docker 跑起來
│
├── data/
│   ├── skills.yaml              # 課程技能清單(200 筆)
│   ├── skills.lance/            # 預 embed 的 LanceDB(git lfs 或 .gitignore)
│   └── skill_graph.gpickle      # 預建的 NetworkX graph
│
├── src/
│   ├── __init__.py
│   ├── embed_skills.py          # Ch3: embedding + LanceDB
│   ├── build_graph.py           # Ch4: kNN → NetworkX
│   ├── chunk_enrich.py          # Ch5: chunk metadata 加 skill labels
│   ├── rag_pipeline.py          # Ch5: query → graph → retrieval
│   ├── recommend_path.py        # Ch6: learning path 推薦(可選)
│   └── utils.py                 # cosine, threshold 工具函數
│
├── notebooks/
│   ├── 01_motivation.ipynb      # Ch1: demo 差異
│   ├── 02_embedding_basics.ipynb    # Ch2: 玩 similarity
│   ├── 03_hyperparam_tuning.ipynb   # Ch4: 調 top_k / threshold
│   ├── 05_rag_comparison.ipynb      # Ch5: before / after 對比
│   └── 06_hybrid_demo.ipynb         # Ch5: Hybrid Retrieval 實驗
│
├── tests/
│   └── test_graph.py            # pytest 驗證 graph 品質
│
└── Makefile                     # make embed, make build, make rag

設計原則


錄播課製作建議

錄製策略

每章腳本模板

問題(30s)          → 為什麼要這樣做?
Code walkthrough(10min) → 邊敲邊解釋
結果展示(5min)      → 跑 query 看效果
邊角 case + 調參(5min)
作業 + 下章預告(1min)

章節影片對應

章節 影片標題 對應檔案 長度 作業
Ch1 為什麼需要 Skill Entity Graph? 01_motivation.ipynb 15min 跑 demo 比較
Ch2 Embedding 夠用的基礎 02_embedding_basics.ipynb 20min embed 自己的技能清單
Ch3 建 Entity Table embed_skills.py 25min 加 10 個新 skill
Ch4 自動建 Graph(核心) build_graph.py 30min 調 threshold 看 cluster
Ch5 接回 Hybrid RAG Pipeline chunk_enrich.py + rag_pipeline.py 35min 測 3 個 career query
Ch6 Bonus: Learning Path + 延伸 notebooks 全 20min fork repo 加功能

學術定位:與 2024–2026 趨勢對齊

這門課教的不是冷門 hack,而是學術界和產業界的主流方向:

我們的做法 vs LightRAG

面向 LightRAG 我們的方式(Entity Embedding)
Graph 邊來源 LLM 自動提取 entity + relationship 向量相似度(cosine > threshold)
自動化程度 高(全自動,但多 LLM call + 成本) 中(只 embed,穩定且便宜)
小資料規模 LLM extract 不穩定 embedding 更穩定
需要外部 API 是(LLM call) 否(本地 sentence-transformers)

如果 skill list 已知,純 embedding 比 LLM extract 穩定,尤其是小資料規模。這就是我們的甜蜜點。


推廣與變現策略

開源 Repo

推廣管道

變現模式

免費     → GitHub repo + 基礎影片 playlist
付費進階 → Cypher / Memgraph 整合、production 部署(Gumroad, NT$300–500)

總結:一張表看完全局

元件 現有方案 課程教的升級
圖結構 NetworkX + 手動 JSON NetworkX + 自動 embedding 建邊
Chunk 檢索 LanceDB vector search LanceDB + skill graph 導航
查詢流程 Vector → top-k chunks Vector → Skill Graph → Graph-guided Chunks
外部依賴 繼續零

核心概念只有一句話:用語意相似度取代關鍵字匹配來建構圖的邊

你的 LanceDB + NetworkX 已經完全夠用。把 semantic kNN graph 做好,就是跟 2024–2026 學術趨勢對齊的做法——只是你在個人 scale 上用幾十行 Python 落地,而不是用大廠框架包起來。學員學完可以直接把 repo 當 portfolio:「我做了一個帶 semantic skill graph 的 career assistant」。


延伸閱讀


Career Knowledge Base 是一個本地優先的履歷知識庫系統,使用 Python + LanceDB + NetworkX + LangChain 建構。