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 整合等各種技能。
使用者會問這樣的問題:
- 「我想做一個 Telegram 聊天機器人,能接 AI 自動回覆,有什麼技術可以參考?」
- 「我用 Python 寫 FastAPI,想部署到 edge 環境降低延遲,有什麼框架推薦?」
- 「我在做網頁自動化擷取資料,除了 Puppeteer 還有什麼方案?」
這些問題有一個共通點:最好的答案往往不在「字面上最相似」的文章裡,而是散落在多個語意相關但用詞不同的素材中。
什麼是 OpenClaw? OpenClaw 是一個跑在本機的個人 AI 助理——用 Node.js 驅動,接 Anthropic / OpenAI 等 LLM API,可以幫你管理日曆、發 email、瀏覽網頁、執行 shell 指令。透過 WhatsApp、Telegram、Discord 等聊天 app 互動,還有社群開發的 Skills & Plugins 擴充系統。我們的知識庫裡有多篇關於它的文章。
讓我們用第一個 query 來看看三種不同的檢索方式如何應對。
方式一:純 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:「我想做一個 Telegram 聊天機器人,能接 AI 自動回覆」
results = vector_search(
"我想做一個 Telegram 聊天機器人,能接 AI 自動回覆,有什麼技術可以參考?",
model
)
for r in results:
print(f" [{r['_distance']:.3f}] {r['title'][:60]}")
結果:
[0.750] Telegram Bot API 整合入門:從建立到部署
[0.762] 從需求推導 Telegram Bot:別先選工具
[0.794] Telegram Bot 踩坑紀錄:高階架構設計專案中的血淚教訓
[0.816] 從 HTTP 到 Telegram Bot:升級你的 Web 工具箱
[0.861] LLM API 整合入門:OpenAI 與 Anthropic Claude 實戰
看起來不錯?仔細看有什麼問題:
- Top 4 全是 Telegram Bot 文章 — 找到了「聊天機器人」但完全沒展開到更深的技術棧
- LLM API 勉強擠進第 5 — 至少沾到「AI 回覆」,但排名墊底
- 完全漏掉的關鍵素材:
- OpenClaw — 它就是一個「Telegram 聊天機器人 + AI 回覆」的完整參考實作!但文章裡描述為「個人 AI 助理」,跟 query 的用詞完全不同
- Agent Architecture — 如果要做「AI 自動回覆」,你需要的不是簡單的 API call,而是一個完整的 Agent 架構(ReAct loop、state management)
- Tool Calling — AI 不只回覆文字,還要「呼叫工具」來查資料、執行動作
- Node.js — Telegram Bot 和 OpenClaw 的執行環境,但沒有任何一篇 Node.js 文章被拉進來
Vector search 找到了字面上最相似的文章,但漏掉了使用者真正需要但不知道怎麼描述的技術。
方式二:Keyword Graph(手動維護 skill-graph.json)
為了解決這個問題,你可能會建一個手寫的技能關係圖:
{
"nodes": ["Telegram Bot", "Discord Bot", "Node.js", "LLM API",
"Agent Architecture", "OpenClaw", "Tool Calling", "Python"],
"edges": [
{"from": "Telegram Bot", "to": "Node.js", "type": "requires"},
{"from": "Telegram Bot", "to": "OpenClaw", "type": "relatedTo"},
{"from": "OpenClaw", "to": "LLM API", "type": "requires"},
{"from": "OpenClaw", "to": "Agent Architecture", "type": "relatedTo"},
{"from": "Agent Architecture", "to": "Tool Calling", "type": "requires"},
{"from": "Agent Architecture", "to": "LLM API", "type": "requires"}
]
}
用 NetworkX 載入後做 graph traversal:
import networkx as nx
import json
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 []
neighbors = {}
for node, d in nx.single_source_shortest_path_length(
graph, skill_name, cutoff=depth
).items():
if node != skill_name:
neighbors[node] = d
return sorted(neighbors.items(), key=lambda x: x[1])
Demo:從 "Telegram Bot" 出發擴展
related = keyword_graph_search("Telegram Bot", G)
for skill, hops in related:
print(f" [{hops} hop] {skill}")
結果:
[1 hop] Node.js(requires)
[1 hop] OpenClaw(relatedTo)
[2 hop] LLM API
[2 hop] Browser Automation
[2 hop] Shell Scripting
[2 hop] Agent Architecture
結構看起來不錯! OpenClaw 被拉進來了,而且透過 OpenClaw 又連到了 Agent Architecture、LLM API、Browser Automation。
但實際拿這些 skill 去導航 chunk 檢索,結果是什麼?
結果(Keyword Graph-Guided):
[0.750] Telegram Bot API 整合入門:從建立到部署 ← via Telegram Bot, Node.js
[0.762] 從需求推導 Telegram Bot:別先選工具 ← via Telegram Bot
[0.794] Telegram Bot 踩坑紀錄:高階架構設計專案中的血淚教訓 ← via Telegram Bot
[0.816] 從 HTTP 到 Telegram Bot:升級你的 Web 工具箱 ← via Telegram Bot
[0.861] LLM API 整合入門:OpenAI 與 Anthropic Claude 實戰 ← via LLM API
跟純 Vector Search 的結果一模一樣。 為什麼?雖然 graph 擴展出了 OpenClaw、Agent Architecture 等技能,但 keyword graph 靠的是字串匹配——知識庫的文章裡如果沒有精確出現那些關鍵字,就撈不到對應的 chunks。
但 Keyword Graph 的三個致命問題
問題 1:人工維護不 scale
知識庫有 81 個 skill,但你的 graph 只覆蓋了 18 個(22%)。
18 個節點的理論最大邊數 = 306,你只手寫了 21 條(6.9%)。
剩下的 63 個 skill?全是盲區。
每次加一個新技能(比如 "Hono"),你得想:它跟 18 個 existing skills 中的哪些有關?然後手動加邊。這不可能持續。
問題 2:關係是主觀的
"Telegram Bot" --requires--> "Node.js" ← 你覺得是前置
"Telegram Bot" --relatedTo--> "OpenClaw" ← 他覺得是平行
"Telegram Bot" --suggests--> "Agent Architecture" ← 她覺得是建議
不同人定義出來的圖完全不同,沒有「客觀標準」。
問題 3:只有結構,沒有語意
Keyword graph 是靠字串匹配的——它知道 "Telegram Bot" 和 "OpenClaw" 有關係,但不知道 "個人 AI 助理"、"聊天機器人"、"chat app 整合" 都是在講同一類技術,也不知道 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",
"Telegram Bot", "Browser Automation", "Puppeteer", "Playwright",
"Shell Scripting", "LangChain", "Agent Architecture", "FastAPI", "Hono",
"Tool Calling", "Discord Bot", "Edge Runtime"]
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):
Telegram Bot ──────0.76──> Discord Bot ← 同類 chat app
Telegram Bot ──────0.74──> OpenClaw ← 都涉及聊天整合
OpenClaw ───────0.83──> Agent Architecture ← 都是 AI Agent
OpenClaw ───────0.78──> LangChain ← Agent 框架生態
Agent Architecture ──0.85──> LangChain ← Agent 建構
Agent Architecture ──0.77──> LLM API ← 底層能力
Agent Architecture ──0.79──> Tool Calling ← Agent 核心能力
LLM API ────────0.88──> OpenAI ← 同一類
LLM API ────────0.86──> Anthropic Claude ← 同一類
Browser Automation ───0.82──> Puppeteer ← 同領域工具
FastAPI ────────0.71──> Hono ← 都是輕量 Web 框架!
注意:Telegram Bot → OpenClaw → Agent Architecture → Tool Calling 這條路徑是 embedding 自動發現的。你不需要手寫任何一條邊。
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(
"我想做一個 Telegram 聊天機器人,能接 AI 自動回覆,有什麼技術可以參考?",
model, skill_table, skill_graph, chunks_table
)
Seed skills: {'Telegram Bot', 'LLM API', 'Discord Bot'}
Expanded skills (+4): {'OpenClaw', 'Agent Architecture',
'Tool Calling', 'Node.js'}
結果:
[0.750] Telegram Bot API 整合入門:從建立到部署
[0.785] OpenClaw 安裝與設定:從零開始部署你的 AI 助理 🎯 新出現!
[0.861] LLM API 整合入門:OpenAI 與 Anthropic Claude 實戰
[0.874] LangChain Agent 架構設計與實作 🎯 新出現!
[0.891] Node.js Event Loop 深入理解:從 libuv 到 async/await 🎯 新出現!
差異一目了然:
- OpenClaw 出現了(透過 graph:Telegram Bot → OpenClaw)— 使用者本來不知道有這個完整的參考實作
- LangChain Agent 架構被拉進來了 — vector search 完全沒找到,graph 從 Agent Architecture 連過來
- Node.js 被拉進來了 — 做 Telegram Bot + OpenClaw 的基礎環境
- 3 篇重複的 Telegram Bot 文章被擠掉了 — 把位置讓給更實用的跨領域結果
Aha Moment:跨越未知的未知
上面的例子已經不錯,但還不夠震撼。真正讓你看出 Graph 威力的,是 Unknown Unknowns 場景——使用者不知道自己不知道什麼。
情境設定
知識庫裡有三篇文章:
- Chunk A:「FastAPI 高併發框架深度指南:從 Pydantic 到 Production...」
- Chunk B:「Express.js 路由設計與中間件模式深入解析...」
- Chunk C:「Hono 實戰:為 Edge Runtime 設計的極速微型 Web 框架...」
注意:Chunk C 完全沒提到 Python 或 FastAPI。使用者根本不知道 "Hono" 這個關鍵字存在。
使用者的 Query
「我用 Python FastAPI 寫 REST API 很熟了,想部署到 Cloudflare Workers 降低延遲,有什麼框架推薦?」
純 Vector Search 的結果
results = vector_search(
"我用 Python FastAPI 寫 REST API,想部署到 Cloudflare Workers 降低延遲,有什麼框架推薦?",
model
)
[0.627] FastAPI 高併發框架深度指南:從 Pydantic 到 Production ← Chunk A ✓ 但使用者已經會了
[0.793] 從需求推導 FastAPI:別先選工具 ← 還是 FastAPI,沒幫助
[0.802] Edge Runtime 全面解析:為什麼要把 Server 搬到邊緣? ← 概念性文章,沒推薦具體框架
[0.825] FastAPI 踩坑紀錄:高階架構設計專案中的血淚教訓 ← 還是 FastAPI
[0.829] 從 Python 到 FastAPI:升級你的 Web 工具箱 ← 還是 Python 生態
Chunk C(Hono 實戰)完全沒出現。5 篇結果中有 3 篇是 FastAPI 相關文章——使用者已經會的東西。為什麼找不到 Hono?因為 Hono 那篇文章的文字沒有提到 Python、FastAPI、REST API——在向量空間裡,它跟 query 的距離太遠了。
有趣的是,#3 的 Edge Runtime 文章在 metadata 裡其實標記了 Hono 這個 skill,但 Hono 自己的實戰文章卻排不進 top-5。使用者不知道 "Hono" 這個關鍵字,所以無論怎麼改寫 query 都找不到它。這就是 Unknown Unknown。
Entity Embedding Graph 的結果
results = entity_graph_search(
"我用 Python FastAPI 寫 REST API,想部署到 Cloudflare Workers 降低延遲,有什麼框架推薦?",
model, skill_table, skill_graph, chunks_table
)
Seed skills: {'FastAPI', 'Edge Runtime', 'Python'}
Expanded skills (+3): {'Hono', 'Cloudflare Workers', 'Node.js'}
^^^^
Graph 自動發現:FastAPI ──0.71──> Hono
結果:
[0.627] FastAPI 高併發框架深度指南:從 Pydantic 到 Production ← Chunk A
[0.802] Edge Runtime 全面解析:為什麼要把 Server 搬到邊緣?
[0.836] Hono 實戰:為 Edge Runtime 設計的極速微型 Web 框架 ← Chunk C 🎯 找到了!
Chunk C 出現了! Graph 做了一件 vector search 做不到的事——它沿著語意邊從 FastAPI 走到 Hono:
FastAPI ──0.71──> Hono(embedding 自動發現:都是輕量 Web 框架)
↓
skill "Hono" 裡的 chunks 被撈出來
↓
Chunk C:Hono 實戰 🎯
系統主動搭了一座橋,把使用者「不知道關鍵字是什麼」的技術精準地遞到他面前。
Graph 不是取代向量搜尋,而是向量搜尋的「導航圖」。
三種方式的完整對比
| 面向 | 純 Vector Search | Keyword Graph | Entity Embedding Graph |
|---|---|---|---|
| 建構成本 | 零(只需 embedding) | 高(手動維護 JSON) | 低(~40 行自動建圖) |
| 關係發現 | ❌ 無結構 | ✅ 有但靠人定義 | ✅ 自動語意發現 |
| 新技能加入 | 自動 | 需手動加邊 | 自動連到相似技能 |
| Unknown Unknowns | ❌ 找不到 | △ 看你有沒有加邊 | ✅ 自動跨越 |
| 需要新套件 | ❌ | ❌ | ❌ |
| 可維護性 | 高 | 低(邊越多越難管) | 高(重新 embed 即可) |
核心概念一句話
用語意相似度取代關鍵字匹配來建構圖的邊。
不是手動定義 "Telegram Bot" --relatedTo--> "OpenClaw",而是讓 embedding 模型告訴你:cosine_similarity(embed("Telegram Bot"), embed("OpenClaw")) = 0.74,自動建邊。
你現有的 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 Demo
用 200 篇知識庫跑 vector search,觀察它找到什麼、漏掉什麼:
# 安裝依賴
pip install lancedb sentence-transformers
# 生成 200 篇 mock data
python generate_career_kb_mock_data.py --out data/career_kb_chunks.jsonl --n 200
# 跑 demo
python demo_vector_search.py
# 額外跑 Aha Moment(FastAPI → Hono 盲區)
python demo_vector_search.py --query2
思考:
- 換不同的 query 試試(用
--query "你的問題") - 把
--top-k從 5 調大到 15,看 OpenClaw 會不會出現?出現在第幾名?
作業 2:手動算 Skill Similarity
from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer("BAAI/bge-m3")
# 計算幾組 skill 的 cosine similarity
skill_pairs = [
("Telegram Bot", "OpenClaw"), # 都涉及聊天 app
("Telegram Bot", "Discord Bot"), # 同類 chat platform
("OpenClaw", "Agent Architecture"), # 都是 AI Agent
("OpenClaw", "Photoshop"), # 完全無關
("Browser Automation", "Puppeteer"), # 同領域工具
("FastAPI", "Hono"), # 不同語言的輕量框架
("LLM API", "Anthropic Claude"), # 上下位概念
]
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 上的邊。哪些你直覺覺得有關,但 similarity 卻很低?
本章重點回顧
- 純 Vector Search 找到的是「字面相似的文章」,但缺乏結構——使用者問 chatbot + AI 回覆,卻找不到 OpenClaw(完整參考實作)和 Agent Architecture(正確的架構模式)
- Keyword Graph(手動 JSON)有結構,但不 scale——90+ 個 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:一門實戰迷你課的完整藍圖 - Next →
從 HelixDB 開始:中小圖教學系列大綱