Ch1:為什麼你的 RAG 需要 Skill Entity Graph?
Ch1:為什麼你的 RAG 需要 Skill Entity Graph?
課程系列:Entity Embedding Graph RAG 迷你課完整藍圖 → Ch1(本篇)
目標:10 分鐘內讓你直覺理解「為什麼不能只做 vector search,也不該手寫 skill-graph.json」。不講理論,直接看 demo 差異。
問題場景:Career Knowledge Base
假設你建了一套 Career Knowledge Base——一個幫助工程師規劃學習路徑的 RAG 系統。知識庫裡有 200 篇技術文章(chunks),涵蓋 Python、Node.js、LLM API、browser automation、chat app 整合等各種技能。
使用者會問這樣的問題:
- 「學 OpenClaw 之前需要先會什麼?」
- 「我會 FastAPI,想轉到 Edge Runtime 生態,有什麼類似的框架?」
- 「想做 AI Agent 開發者,下一步該學什麼?」
這些問題有一個共通點:答案不只在一篇文章裡,而是散落在多個語意相關的素材中。
什麼是 OpenClaw? OpenClaw 是一個跑在本機的個人 AI 助理——用 Node.js 驅動,接 Anthropic / OpenAI 等 LLM API,可以幫你管理日曆、發 email、瀏覽網頁、執行 shell 指令。透過 WhatsApp、Telegram、Discord 等聊天 app 互動,還有社群開發的 Skills & Plugins 擴充系統。
讓我們看看三種不同的檢索方式如何應對。
方式一:純 Vector Search(你現在大概在這裡)
最直覺的做法——把 query 做 embedding,去 LanceDB 找最相似的 chunks:
import lancedb
db = lancedb.connect("data/career.lance")
chunks_table = db.open_table("chunks")
def vector_search(query, model, top_k=5):
"""純 vector search:query embedding → 找最相似的 chunks。"""
query_vec = model.encode([query])[0]
results = chunks_table.search(query_vec).limit(top_k).to_list()
return results
Demo:「學 OpenClaw 之前需要先會什麼?」
results = vector_search("學 OpenClaw 之前需要先會什麼?", model)
for r in results:
print(f" [{r['_distance']:.3f}] {r['title'][:60]}")
結果:
[0.185] OpenClaw 安裝與設定:從零開始部署你的 AI 助理
[0.198] 打造自訂 OpenClaw Skills:Plugin 開發指南
[0.237] LangChain Agent 架構設計與實作
[0.261] Telegram Bot API 整合入門
[0.294] Python asyncio 深入理解
問題出在哪?
表面上看結果不錯——確實找到了 OpenClaw 相關的文章。但仔細看:
- 缺乏結構:結果只是「按相似度排序的 5 篇文章」,沒有告訴你前置技能的「先後關係」
- 只看字面相似:排第一的是 OpenClaw 安裝指南(因為直接提到 OpenClaw),但使用者問的是「先學什麼」——Node.js 基礎和 LLM API 概念才是前置技能,卻被擠到後面或根本沒出現
- 漏掉隱含需求:學 OpenClaw 之前需要懂的 browser automation(它的核心功能之一:網頁瀏覽、表單填寫、資料擷取)和 shell 指令基礎(它有 full system access)完全沒出現,因為那些文章在向量空間裡跟 "OpenClaw" 不夠近
純 vector search 擅長「找到字面上相關的東西」,但不擅長「理解事物之間的結構關係」。
方式二:Keyword Graph(手動維護 skill-graph.json)
為了解決結構問題,你可能會建一個手寫的技能關係圖:
{
"nodes": ["OpenClaw", "Node.js", "LLM API", "Telegram Bot",
"Browser Automation", "Shell Scripting", "Python"],
"edges": [
{"from": "OpenClaw", "to": "Node.js", "type": "requires"},
{"from": "OpenClaw", "to": "LLM API", "type": "requires"},
{"from": "OpenClaw", "to": "Browser Automation", "type": "requires"},
{"from": "OpenClaw", "to": "Shell Scripting", "type": "requires"},
{"from": "OpenClaw", "to": "Telegram Bot", "type": "relatedTo"},
{"from": "Telegram Bot", "to": "Node.js", "type": "requires"}
]
}
用 NetworkX 載入後做 graph traversal:
import networkx as nx
import json
# 載入手動維護的 skill graph
with open("data/skill-graph.json") as f:
data = json.load(f)
G = nx.DiGraph()
for edge in data["edges"]:
G.add_edge(edge["from"], edge["to"], type=edge["type"])
def keyword_graph_search(skill_name, graph, depth=2):
"""沿著手寫的 keyword graph 找到前置技能。"""
if skill_name not in graph:
return []
# 找所有 depth 步以內的鄰居
predecessors = {}
for node, d in nx.single_source_shortest_path_length(
graph, skill_name, cutoff=depth
).items():
if node != skill_name:
predecessors[node] = d
return sorted(predecessors.items(), key=lambda x: x[1])
Demo:同一個 query
prereqs = keyword_graph_search("OpenClaw", G)
for skill, hops in prereqs:
print(f" [{hops} hop] {skill}")
結果:
[1 hop] Node.js
[1 hop] LLM API
[1 hop] Browser Automation
[1 hop] Shell Scripting
[1 hop] Telegram Bot
[2 hop] Node.js(透過 Telegram Bot)
好多了! 現在我們知道 OpenClaw 的前置技能包含 Node.js、LLM API、Browser Automation、Shell Scripting,而且有結構關係。
但 Keyword Graph 的三個致命問題
問題 1:人工維護不 scale
你有 200 個 skill,理論上有 200 × 199 = 39,800 種可能的關係。
你真的要一條一條手寫?
每次加一個新技能(比如 "Hono"),你得想:它跟 200 個 existing skills 中的哪些有關?然後手動加邊。這不可能持續。
問題 2:關係是主觀的
"OpenClaw" --requires--> "Node.js" ← 你覺得是前置
"OpenClaw" --relatedTo--> "LangChain" ← 他覺得是平行
"OpenClaw" --implies--> "AI Agent" ← 她覺得是蘊含
不同人定義出來的圖完全不同,沒有「客觀標準」。
問題 3:只有結構,沒有語意
Keyword graph 是靠字串匹配的——它知道 "OpenClaw" 和 "Node.js" 有關係,但不知道 "browser control"、"Puppeteer"、"Playwright" 都是在講同一類技術,也不知道 OpenClaw 的 Skill 系統跟 LangChain 的 Tool 概念有多接近。
方式三:Entity Embedding Graph(本課程教的方法)
核心想法:不用手寫關係,讓 embedding 的語意相似度自動發現技能之間的連結。
from sentence_transformers import SentenceTransformer
import lancedb
import networkx as nx
model = SentenceTransformer("BAAI/bge-m3")
# ① 對所有 skill 做 embedding
skills = ["OpenClaw", "Node.js", "LLM API", "Anthropic Claude", "OpenAI API",
"Telegram Bot", "Browser Automation", "Puppeteer", "Playwright",
"Shell Scripting", "LangChain", "AI Agent", "FastAPI", "Hono"]
vectors = model.encode(skills)
# ② 存進 LanceDB
db = lancedb.connect("data/skills.lance")
skill_table = db.create_table("skills", [
{"name": s, "vector": v.tolist()}
for s, v in zip(skills, vectors)
], mode="overwrite")
# ③ 用相似度自動建圖(零手動!)
def build_entity_graph(table, top_k=5, threshold=0.3):
"""用 embedding cosine similarity 自動建立 skill 之間的語意邊。"""
skills_data = table.to_pandas()
graph = nx.Graph()
for _, row in skills_data.iterrows():
graph.add_node(row["name"])
for _, row in skills_data.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=round(similarity, 3))
return graph
skill_graph = build_entity_graph(skill_table)
自動發現的語意邊
不用手寫,embedding 自動產生這樣的圖:
自動產生的邊(基於 cosine similarity):
OpenClaw ─────0.83──> AI Agent ← 都是 AI 助理
OpenClaw ─────0.78──> LangChain ← Agent 框架生態
OpenClaw ─────0.74──> Telegram Bot ← 聊天 app 整合
AI Agent ─────0.85──> LangChain ← Agent 建構
AI Agent ─────0.77──> LLM API ← 底層能力
LLM API ──────0.88──> OpenAI API ← 同一類
LLM API ──────0.86──> Anthropic Claude ← 同一類
Browser Automation ──0.82──> Puppeteer ← 同領域工具
Browser Automation ──0.80──> Playwright ← 同領域工具
FastAPI ──────0.71──> Hono ← 都是輕量 Web 框架!
注意到了嗎?OpenClaw 和 LangChain 被自動連在一起——它們都是 AI Agent 框架生態的一部分。Browser Automation 自動跟 Puppeteer、Playwright 連上——因為語意空間知道它們是同一類東西。這些關係你手動寫 skill-graph.json 不一定會全部想到。
Demo:同一個 query,用 Entity Graph
def entity_graph_search(query, model, skill_table, skill_graph,
chunks_table, top_k=5, hops=2):
"""
Entity Embedding Graph 檢索流程:
Query → Seed Skills → Graph Expansion → Guided Chunk Retrieval
"""
query_vec = model.encode([query])[0]
# Step 1: 找 seed skills(entity-level)
seed_results = skill_table.search(query_vec).limit(3).to_list()
seed_skills = {r["name"] for r in seed_results}
print(f" Seed skills: {seed_skills}")
# Step 2: 沿 graph 擴展 N hop
expanded = set()
for skill in seed_skills:
if skill in skill_graph:
neighbors = nx.single_source_shortest_path_length(
skill_graph, skill, cutoff=hops
)
expanded.update(neighbors.keys())
all_skills = seed_skills | expanded
print(f" Expanded skills (+{len(expanded - seed_skills)}): {expanded - seed_skills}")
# Step 3: 用 skill set 導航 chunk 檢索
chunks = chunks_table.search(query_vec).limit(top_k * 3).to_list()
guided = [c for c in chunks
if any(s.lower() in c["text"].lower() for s in all_skills)][:top_k]
return guided
results = entity_graph_search(
"學 OpenClaw 之前需要先會什麼?",
model, skill_table, skill_graph, chunks_table
)
Seed skills: {'OpenClaw', 'AI Agent', 'LangChain'}
Expanded skills (+5): {'LLM API', 'Node.js', 'Telegram Bot',
'Browser Automation', 'Anthropic Claude'}
結果:
[0.185] OpenClaw 安裝與設定:從零開始部署你的 AI 助理
[0.221] Node.js 非同步程式設計:Event Loop 與 Promise 深入理解
[0.245] LLM API 整合實戰:Anthropic Claude 與 OpenAI 呼叫模式
[0.267] Browser Automation 入門:Puppeteer 網頁操控與資料擷取
[0.278] Telegram Bot 開發:從 BotFather 到自動化工作流
差異一目了然:
- Node.js 基礎和 LLM API 被拉進來了(透過 graph expansion:OpenClaw → AI Agent → LLM API)
- Browser Automation 也出現了(OpenClaw 的核心功能之一)
- 結果有了「結構」——不只是「跟 OpenClaw 字面相似的東西」,而是「學 OpenClaw 的前置技能鏈」
Aha Moment:跨越未知的未知
上面的例子已經不錯,但還不夠震撼。真正讓你看出 Graph 威力的,是 Unknown Unknowns 場景——使用者不知道自己不知道什麼。
情境設定
知識庫裡有三篇文章:
- Chunk A:「FastAPI 是一個基於 Python 的現代 Web 框架,主打高併發與自動生成文件...」
- Chunk B:「Node.js 伺服器效能調校與架構優化指南...」
- Chunk C:「Hono 實戰:為 Edge Runtime 設計的極速微型 Web 框架...」
注意:Chunk C 完全沒提到 Python 或 FastAPI。
使用者的 Query
「我平常用 Python 寫 FastAPI,現在想在 JavaScript/Edge 生態找一個類似的輕量、高效能 API 框架,有什麼學習資源?」
純 Vector Search 的結果
results = vector_search(
"我平常用 Python 寫 FastAPI,想找 JavaScript Edge 類似的輕量高效能 API 框架",
model
)
[0.201] FastAPI 是一個基於 Python 的現代 Web 框架... ← Chunk A ✓ 但使用者已經知道了
[0.234] Node.js 伺服器效能調校與架構優化指南... ← Chunk B △ 沾邊但不對
[0.267] Express.js 路由設計與中間件... ← 老框架,不是使用者要的
[0.289] Python asyncio 深入理解... ← 完全無關
[0.301] Django REST Framework API 設計... ← 還是 Python,沒幫助
Chunk C(Hono 實戰)完全沒出現。為什麼?因為 Hono 那篇文章的文字沒有提到 Python、FastAPI——在向量空間裡,它跟 query 的距離太遠了。
Entity Embedding Graph 的結果
results = entity_graph_search(
"我平常用 Python 寫 FastAPI,想找 JavaScript Edge 類似的輕量高效能 API 框架",
model, skill_table, skill_graph, chunks_table
)
Seed skills: {'FastAPI', 'Python', 'Edge Runtime'}
Expanded skills (+3): {'Hono', 'Node.js', 'OpenClaw'}
^^^^
Graph 自動發現:FastAPI → Hono(都是輕量 Web 框架)
結果:
[0.201] FastAPI 是一個基於 Python 的現代 Web 框架... ← Chunk A
[0.234] Node.js 伺服器效能調校... ← Chunk B
[0.245] Hono 實戰:為 Edge Runtime 設計的極速微型框架... ← Chunk C 🎯 找到了!
Chunk C 出現了! Graph 做了一件 vector search 做不到的事——它沿著語意邊從 FastAPI 走到 Hono:
FastAPI ──0.71──> Hono(都是輕量 Web 框架)
↓
skill "Hono" 裡的 chunks 被撈出來
↓
Chunk C:Hono 實戰 🎯
系統主動搭了一座橋,把使用者「不知道關鍵字是什麼」的技術精準地遞到他面前。
Graph 不是取代向量搜尋,而是向量搜尋的「導航圖」。
三種方式的完整對比
| 面向 | 純 Vector Search | Keyword Graph | Entity Embedding Graph |
|---|---|---|---|
| 建構成本 | 零(只需 embedding) | 高(手動維護 JSON) | 低(~40 行自動建圖) |
| 關係發現 | ❌ 無結構 | ✅ 有但靠人定義 | ✅ 自動語意發現 |
| 新技能加入 | 自動 | 需手動加邊 | 自動連到相似技能 |
| Unknown Unknowns | ❌ 找不到 | △ 看你有沒有加邊 | ✅ 自動跨越 |
| 需要新套件 | ❌ | ❌ | ❌ |
| 可維護性 | 高 | 低(邊越多越難管) | 高(重新 embed 即可) |
核心概念一句話
用語意相似度取代關鍵字匹配來建構圖的邊。
不是手動定義 "OpenClaw" --requires--> "Node.js",而是讓 embedding 模型告訴你:cosine_similarity(embed("OpenClaw"), embed("AI Agent")) = 0.83,自動建邊。
你現有的 LanceDB + NetworkX 已經完全夠用。零新依賴,~40 行 Python 就能實現。
這門課接下來教什麼?
| 章節 | 標題 | 你會學到 |
|---|---|---|
| Ch1 | 為什麼需要 Skill Entity Graph?(本篇) | 三種方式的差異 + Aha Moment |
| Ch2 | Embedding 夠用的基礎 | sentence-transformers、cosine similarity、模型選擇 |
| Ch3 | Skill List → Entity Table | 用 LanceDB 存 skill embeddings |
| Ch4 | 自動建 Graph(核心) | kNN → NetworkX,threshold 調參 |
| Ch5 | 接回 Hybrid RAG Pipeline | Chunk Enrichment + Graph-guided Retrieval |
| Ch6 | Bonus: Learning Path 推薦 | PageRank、最短路推薦下一步技能 |
動手試試:Ch1 作業
作業 1:觀察 Vector Search 的盲區
用你自己的知識庫(或用以下 mock data),跑一個 query,觀察 vector search 漏掉了什麼:
from sentence_transformers import SentenceTransformer
import lancedb
model = SentenceTransformer("BAAI/bge-m3")
# Mock 知識庫
chunks = [
{"id": 1, "title": "OpenClaw 安裝與設定指南",
"text": "OpenClaw 是跑在本機的個人 AI 助理,用 Node.js 驅動,接 Anthropic 或 OpenAI API。支援 WhatsApp、Telegram 等聊天 app 互動。"},
{"id": 2, "title": "Node.js Event Loop 深入理解",
"text": "Node.js 事件迴圈機制:libuv、非阻塞 I/O、Promise 與 async/await 的執行順序。"},
{"id": 3, "title": "Puppeteer 網頁自動化實戰",
"text": "Puppeteer 是 Google 開發的 headless Chrome 控制工具,可以自動瀏覽網頁、填表單、截圖、擷取資料。"},
{"id": 4, "title": "LLM API 整合入門",
"text": "如何呼叫 OpenAI 和 Anthropic Claude API:token 管理、streaming response、function calling。"},
{"id": 5, "title": "FastAPI 高併發框架入門",
"text": "FastAPI 是基於 Python 的現代 Web 框架,使用 Pydantic 進行資料驗證,支援 async/await 高併發。"},
{"id": 6, "title": "Hono 實戰指南",
"text": "Hono 是為 Edge Runtime 設計的超輕量 Web 框架,支援 Cloudflare Workers、Deno Deploy。極速路由、零依賴。"},
]
# Embed 並存入 LanceDB
vectors = model.encode([c["text"] for c in chunks])
for c, v in zip(chunks, vectors):
c["vector"] = v.tolist()
db = lancedb.connect("/tmp/ch1_demo")
table = db.create_table("chunks", chunks, mode="overwrite")
# 查詢
query = "學 OpenClaw 之前需要先會什麼?"
query_vec = model.encode([query])[0]
results = table.search(query_vec).limit(5).to_list()
print("=== 純 Vector Search 結果 ===")
for r in results:
print(f" [{r['_distance']:.3f}] {r['title']}")
思考:Node.js、Puppeteer(browser automation)、LLM API 這些前置技能有出現在 top 5 嗎?排名第幾?
作業 2:手動算 Skill Similarity
# 計算幾組 skill 的 cosine similarity
skill_pairs = [
("OpenClaw", "AI Agent"), # 同類產品
("OpenClaw", "LangChain"), # Agent 生態
("OpenClaw", "Photoshop"), # 完全無關
("Browser Automation", "Puppeteer"), # 同領域工具
("FastAPI", "Hono"), # 不同語言的輕量框架
("LLM API", "Anthropic Claude"), # 上下位概念
]
import numpy as np
for s1, s2 in skill_pairs:
v1 = model.encode([s1])[0]
v2 = model.encode([s2])[0]
sim = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
print(f" {s1} ↔ {s2}: {sim:.3f}")
思考:哪些 pair 的 similarity > 0.7?這些自然就會變成 entity graph 上的邊。
本章重點回顧
- 純 Vector Search 擅長「找到字面相關」,但缺乏結構,容易漏掉語意相關但字面不同的素材
- Keyword Graph(手動 JSON)有結構,但不 scale——200 個 skill 就要手動維護上千條關係
- Entity Embedding Graph 是兩者的最佳折衷:
- 自動建邊(零手動維護)
- 有語意結構(支援 multi-hop traversal)
- 能發現 Unknown Unknowns(FastAPI → Hono)
- 零新依賴(LanceDB + NetworkX 就夠)
下一章:Ch2 — Embedding 夠用的基礎,我們會深入了解 embedding model 如何選擇、similarity 如何衡量,為 Ch4 的自動建圖打好地基。
延伸閱讀
- Entity Embedding Graph RAG 迷你課完整藍圖 — 整門課程的總覽
- Memgraph vs HelixDB vs 手刻 Semantic Graph — 為什麼 200 nodes 不需要圖資料庫
- GraphRAG 資料庫新勢力:Lance-graph vs HelixDB vs FalkorDB — 新一代圖向量資料庫評測
Entity Embedding Graph RAG 迷你課是一個 project-based 教學系列,使用 Python + LanceDB + NetworkX 零新依賴打造帶語意圖的 Career RAG 系統。
- ← Previous
從零打造 Entity Embedding Graph RAG:一門實戰迷你課的完整藍圖