Ian Chou's Blog

CtxFST CH22 - Entity-Aware Retrieval vs Pure RAG:用真實筆記集跑一次,差在哪裡

CtxFST CH22:Entity-Aware Retrieval vs Pure RAG,用真實筆記集跑一次,差在哪裡

CH21 為止,我們其實已經把 CtxFST 的骨架長得很完整了。

前半段,我們做了:

後半段,我們又做了:

如果只看 architecture,這套系統已經很豐富。

但也正因為它越來越完整,一個更實際的問題就會變得越來越尖銳:

如果我今天只是想把自己的筆記丟進去做檢索,為什麼我要用 CtxFST,而不是直接做一條普通的 pure RAG?

這個問題,前面 21 章其實一直沒有正面回答。

CH10 很接近,但它更像是 concept demo:它證明 entity embedding graph 這個方向有潛力,能做 graph expansion,也能打開 unknown unknowns。

但它還不是一章真正的 benchmark chapter。

也就是說,到目前為止,我們大多證明的是:

卻還沒有用一個更接近真實使用者工作流的方式,正面回答:

它真的比 pure vector retrieval 更值得用嗎?

這就是 CH22 要做的事情。


先講一句最短的結論

這一章最值得記住的一句話是:

CtxFST 的價值,不是把 chunks 換個地方存,而是讓 retrieval 從「找相似文字」升級成「沿著 entity 理解問題真正指向什麼」。

這個差別,在 query 很直接的時候,可能看起來沒有那麼大。

但一旦 query 開始變成:

pure RAG 的極限就會很快出現。

而這正是 entity-aware retrieval 開始拉開差距的地方。


這一章不是 feature chapter,而是 proof chapter

這一章不新增任何 runtime feature。

沒有新的 planner。
沒有新的 relation type。
也沒有新的 OpenClaw integration layer。

我們只做一件事:

拿一份真的會讓 pure RAG 出錯的知識庫,並排比較兩條 pipeline 的結果。

如果這一章做得好,它在整個系列裡的角色會非常關鍵。

因為它不是再往架構圖上疊一層能力,而是第一次建立一個很重要的 anchor:

entity-aware retrieval 不是概念上比較酷,而是在真實筆記上真的比較有用。


實驗資料:一份 200 篇的真實知識庫

這次的 corpus 是一份個人開發者知識庫,主題包含:

整體規模:

面向 數量
chunks 200
tracked skills / entities 55
entity graph 邊數 165
benchmark queries 6

這個規模已經夠讓幾種很典型的 retrieval failure 浮現出來。

最重要的是,這批資料有一個關鍵特徵:

很多真正相關的 chunk,並不會直接寫出使用者 query 裡的原詞。

例如:

這種情況,正是 pure lexical retrieval 最容易漏掉的地方。


我們比較的兩條 pipeline

這次比較的流程非常簡單,而且故意只比最核心的 retrieval substrate。

Baseline:TF-IDF Cosine Retrieval

query
-> TF-IDF tokenize
-> cosine similarity against all chunk vectors
-> top-3 chunks

Baseline 是一條純文字比對 pipeline,用英文 tokenizer(\b[a-zA-Z][a-zA-Z0-9_-]{1,}\b)做 TF-IDF,計算 query 與每個 chunk 的 cosine 相似度。

這個設計和很多最小可用 RAG 系統實際在做的事情本質相同——只是把神經網路 embedding 換成了統計向量。核心限制是一樣的:只有字面重疊才會得高分。

CtxFST:Entity-Aware Retrieval

query
-> entity matching(TF-IDF on entity profiles + query expansion)
-> top entity seeds(degree-normalized)
-> entity graph fan-out(1 hop, with degree discount)
-> chunk coverage score(sqrt-normalized by chunk entity count)
-> combine graph signal + baseline score
-> top-3 chunks

CtxFST 多問了一層:

這個 query 真正碰到的是哪些 entity?
這些 entity 在圖裡連到哪些相關概念?
哪些 chunks 雖然字面不像,但其實在同一個語意區域?

這一層,就是整個差別的來源。


兩種不同的勝因,先講清楚

在看具體結果之前,我需要先誠實說明一件重要的事。

這 6 個 query 的結果,勝因來自兩種完全不同的機制。把它們混在一起說是不誠實的。

Type A:Tokenizer 限制造成的 baseline 失敗(Q2、Q6)

這兩個 query 全部用中文寫成,baseline 的英文 tokenizer 無法從中提取任何有效 token。結果就是 baseline 的所有 chunk 分數都是 0.000,等於隨機排序。

CtxFST 之所以贏,不是因為 entity graph 更聰明,而是因為它有一個 entity vocabulary 作為中間層,能把「聊天機器人」這個中文詞 resolve 到 entity:telegram-botentity:discord-bot,再從那裡展開。

這是一種架構優勢,但不是 retrieval algorithm 的核心 proof。如果換一個能處理中文的 tokenizer(jieba 加英中混合),baseline 的結果會完全不同。

Type B:Entity graph traversal 跨過了真實的 lexical gap(Q1、Q3、Q5)

這三個才是真正的 evidence。Query 裡有足夠的英文或可 tokenize 的詞,baseline 能夠作動,但它選的方向是錯的,或排序不如 CtxFST 精準。這裡看到的差距,才是 entity-aware retrieval 真正贏得的東西。

下面按這個順序講:先講 Type B,再誠實交代 Type A。


主角 Q1:「用 AI 做語意搜尋」——baseline 跑向錯誤的 cluster

Query:我有哪些跟「用 AI 做語意搜尋」相關的筆記?

這個 query 的目的是找出知識庫裡關於語意搜尋生態的筆記——Embeddings、Vector DB、RAG、Semantic Search、LanceDB 這個 cluster。

Pure RAG Top-3

排名 Doc ID Chunk 分數
1 doc_43931a796cf75674 從需求推導 OpenClaw:別先選工具 0.196
2 doc_98a7e45e0bf652a4 OpenClaw 與 Discord Bot 的邊界:怎麼拆才不會痛苦 0.192
3 doc_7ef4df22bb7d5ce2 OpenClaw 安裝與設定:從零開始部署你的 AI 助理 0.135

命中預期 entities:0 / 3

Baseline 被 "AI" 這個高頻詞主導,吸去了包含大量 "AI" 字樣的 OpenClaw 相關 chunk。它選的方向完全不對。

CtxFST Top-3

排名 Doc ID Chunk 分數
1 doc_9b60bf368b7a579a 從需求推導 Embeddings:別先選工具 0.980
2 doc_470f1eb51cba58a2 從 NLP Concepts 到 Embeddings:升級你的 AI 工具箱 0.901
3 doc_5117fe624dac5b25 從 Linear Algebra Basics 到 Embeddings:升級你的 AI 工具箱 0.884

命中預期 entities:3 / 3

這裡發生了什麼

entity matching 的路徑是這樣的:

「用 AI 做語意搜尋」
-> entity:semantic-search(score 0.377)
-> entity:embeddings(score 0.194,via graph fan-out)
-> entity:vector-db(score 0.182,via graph fan-out)

「語意搜尋」透過 ENTITY_QUERY_EXPANSIONS 被 resolve 到 ["semantic", "search", "embeddings", "vector", "retrieval", "similarity"],然後 entity matching 找到了 entity:semantic-search,再沿 entity graph 展開到 entity:embeddingsentity:vector-db

這條路徑裡完全沒有 "OpenClaw" 出現,所以 CtxFST 不會被 "AI" 這個詞拉去 agent runtime 方向。

要補充說明的是:三個 top chunk 都聚焦在 Embeddings 這支,沒有把 RAG 或 LanceDB 帶出來。Entity fan-out 選擇了 entity:embeddings 為中心,而不是把整個向量搜尋生態都展開。這是這份 benchmark 的邊界,在更大的 corpus 或更深的 fan-out 上,這個問題會改善。

但關鍵是:baseline 的方向完全錯了,CtxFST 的方向是對的。


主角 Q5:「Python Web 框架」——"Web" 這個詞讓 baseline 走錯路

Query:我對 Python Web 框架懂多少?

Pure RAG Top-3

排名 Doc ID Chunk 分數
1 doc_515ee377094c5a32 Web Standards 與 軟體工程 的邊界:怎麼拆才不會痛苦 0.530
2 doc_7a25af349fa35142 Web Standards 踩坑紀錄:Edge Runtime 專案中的血淚教訓 0.500
3 doc_bf19ea16b6b05e07 從 Web Standards 到 Edge Runtime:升級你的 Web 工具箱 0.334

命中預期 entities(Python、FastAPI、Flask、Django、Pydantic):0 / 3

Baseline 的問題很清楚:query 裡有 "Web",TF-IDF 就抓住這個詞,找到 Web Standards 和 Edge Runtime 相關的 chunk。這些是真實的 "Web" 內容,但不是「Python Web 框架」。

CtxFST Top-3

排名 Doc ID Chunk 分數
1 doc_515ee377094c5a32 Web Standards 與 軟體工程 的邊界:怎麼拆才不會痛苦 0.933
2 doc_7a25af349fa35142 Web Standards 踩坑紀錄:Edge Runtime 專案中的血淚教訓 0.913
3 doc_66422274129957d5 從需求推導 Python:別先選工具 0.801

命中預期 entities:1 / 3

CtxFST 的 #1 和 #2 跟 baseline 一樣,仍然是 Web Standards chunk。但它在第三名拉進了 doc_66422274129957d5(從需求推導 Python:別先選工具),這個 chunk 帶有 entity:pythonentity:fastapi

要補充說明的是:這篇是「怎麼開始學 Python」的入門選擇指引,不是 FastAPI 框架的深入教學。它帶著 entity:fastapi 是因為 entity pipeline 標記了它和 FastAPI 的關聯,但如果你真的在找 FastAPI 的實作筆記,這篇不是最直接的答案。

這裡發生了什麼

Entity matching 的 top entity 是 entity:web-standards(score 0.130),因為 query 裡的 "Web" 還是會命中它。但 entity:pip(0.108)和 entity:virtual-environments(0.093)也有入選,因為它們跟 Python ecosystem 相關。

這讓 Python 的 chunk 有機會透過 entity coverage 拉到第三名,即使 "Python" 在 baseline 的 TF-IDF 排名裡不如 "Web" 強。

這是一個 entity disambiguation 的案例:

這個差距是真實的、可重現的。CtxFST 的方向是對的,即使這次找到的那篇 Python chunk 不是最精準的答案。


配角 Q3:「AI agent runtime」——相同 cluster,更好的排序

Query:要理解一個完整的 AI agent runtime,我需要哪些底層知識?

Pure RAG Top-3

排名 Doc ID Chunk 分數
1 doc_50612e609bd05175 LangChain Agent 架構設計與實作 0.546
2 doc_41d2434d28525f22 從需求推導 Agent Architecture:別先選工具 0.515
3 doc_4839fc8f779e5165 從 LLM API 到 Agent Architecture:升級你的 AI 工具箱 0.512

命中預期 entities(agent-architecture、llm-api、tool-calling 等):3 / 3

Baseline 其實也找到了相關 chunk,都命中了。問題在第一名。

CtxFST Top-3

排名 Doc ID Chunk 分數
1 doc_41d2434d28525f22 從需求推導 Agent Architecture:別先選工具 1.366
2 doc_4839fc8f779e5165 從 LLM API 到 Agent Architecture:升級你的 AI 工具箱 1.364
3 doc_e28f69c1f4825bfe 從 Tool Calling 到 Agent Architecture:升級你的 AI 工具箱 1.290

命中預期 entities:3 / 3

差異在哪

Baseline 的 #1 是 LangChain Agent 架構設計與實作,這是一篇使用特定框架的實作文,適合已經知道方向的人,不太適合「需要哪些底層知識」這個問題的語意。

CtxFST 把它換下去,換上 從 Tool Calling 到 Agent Architecture 這篇,這是一篇明確描述 tool calling → agent architecture 升級路徑的文章,更直接回應 "底層知識" 這個需求。

Entity fan-out 選中了:entity:agent-architecture(0.155)、entity:tool-calling(0.138)、entity:state-management(0.030),這些 entity 在 graph 裡有明確的連結關係,把 "底層知識" 這個抽象需求 resolve 到了更精準的 chunk。

這不是 "baseline 找不到相關結果" 的勝利,而是 graph-aware reranking 的勝利:相同的 cluster 裡,CtxFST 選出了更符合 query 意圖的 chunk。


誠實交代 Q2 和 Q6:Tokenizer 造成的 baseline 失敗

Q2:「我想做一個聊天機器人,需要先學哪些基礎?」

Baseline 所有 chunk 分數 0.000,排序等於隨機。CtxFST 找到了:

排名 Chunk
1 從需求推導 Telegram Bot:別先選工具
2 從 HTTP 到 Telegram Bot:升級你的 Web 工具箱
3 Telegram Bot 踩坑紀錄:高階架構設計 專案中的血淚教訓

Q6:「學了網頁操作自動化之後,還能拿來做哪些不只是爬蟲的事?」

同樣,baseline 分數全是 0.000。CtxFST 找到了:

排名 Chunk
1 從需求推導 Browser Automation:別先選工具
2 Browser Automation 踩坑紀錄:OpenClaw 專案中的血淚教訓
3 Browser Automation 入門:從 0 到能用的最小專案

為什麼我不把這兩個算成 entity-aware retrieval 的核心優勢

原因很直接:如果把 baseline 的 tokenizer 換成能處理中文的版本,這個差距大機率會消失。Baseline 的失敗不是因為它的 retrieval logic 不好,是因為它的 tokenizer 壞掉了。

但這裡還是有一件值得記住的事:

CtxFST 的 entity vocabulary 是語言中立的。

「聊天機器人」透過 ENTITY_QUERY_EXPANSIONS 被 resolve 到 ["telegram", "discord", "bot", "chatbot"],再命中 entity:telegram-botentity:discord-bot,不依賴 chunk text 裡有沒有 "聊天機器人" 這個詞。

要說明的是:ENTITY_QUERY_EXPANSIONS 是手動維護的 dictionary,不是自動學習的。在這份 benchmark 裡,它是刻意手寫的,目的是確保結果可重現、可驗證,而不是讓系統看起來比實際更聰明。在 production 環境中,這層可以替換成 LLM-based entity recognition 或 embedding similarity lookup,讓 resolve 過程變成自動的。

這個 entity 中間層的設計本身是架構優勢——它讓 retrieval 不依賴 query 語言和 corpus 語言必須一致。即使不是本次 benchmark 核心 proof 點,它說明了為什麼 entity pipeline 比純文字比對多了一層可操作的語意空間。


補充 Q4:兩邊都找到了,CtxFST 略好

Query:前端工程師想接觸 LLM 開發,有什麼入門路徑?

方法 Top-3 命中
Baseline 從需求推導 LLM API、從 LLM API 到 Tool Calling、從 HTTP 到 LLM API 3/3
CtxFST 從需求推導 LLM API、從 HTTP 到 LLM API、LLM API 與 OpenAI 的邊界 3/3

差異:CtxFST 在 #3 換入了 LLM API 與 OpenAI 的邊界 這篇(討論如何區分 LLM API 和 OpenAI 的責任邊界),baseline 的 #3 是 從 LLM API 到 Tool Calling(往 tool calling 方向走)。前者對「入門路徑」的語意更貼合,但這是個小差異。兩邊都有找到相關 chunk。


整體結果

方法 Hit@3 Chunks recovered Missed
Pure RAG 3 / 6 7 / 18 11
CtxFST entity-aware 6 / 6 16 / 18 2

讀這個表要搭配下面這個備注:


CtxFST 真正贏在哪裡?

如果把這一章的觀察濃縮成幾點,CtxFST 主要贏在三件事。

1. 它不讓單一高頻詞主導 query 解讀

Q1 裡,"AI" 這個詞在 baseline 的世界裡吸走了所有 signal,所以找到的全是 OpenClaw(一個很常提 "AI" 的 topic)。CtxFST 透過 entity matching 把 query resolve 到 entity:semantic-search,不受 "AI" 的字面頻率影響。

2. 它能在相同的 cluster 裡選出更符合 query 意圖的 chunk

Q3 的例子說明了這一點:兩條 pipeline 都找到了 agent 相關 chunk,但 CtxFST 優先選出了描述 "prerequisite chain" 的 chunk(Tool Calling → Agent Architecture),而 baseline 的第一名是一篇 LangChain 實作介紹。

3. Entity vocabulary 是語言中立的中間層

即使 baseline 可以用更好的中文 tokenizer,entity graph 的核心 value proposition 是:它讓 retrieval 不依賴 query 詞和 chunk 詞的直接重疊。使用者說的「聊天機器人」和 chunk 裡說的 "Telegram Bot" 可以透過 entity 連起來,不需要有人翻譯。


如果你想自己重跑

這份 benchmark 的所有 artifacts 和 runner 都在 repo 裡:

# 跑 career KB benchmark
python3 scripts/ch22_retrieval_benchmark.py \
  examples/career-kb-benchmark/chunks.json \
  examples/career-kb-benchmark/entity-profiles.json \
  examples/career-kb-benchmark/entity-graph.json \
  examples/career-kb-benchmark/queries.yaml \
  --report-output examples/career-kb-benchmark/evaluation-results.md \
  --json-output examples/career-kb-benchmark/retrieval-results.json

Runner 是純 Python 標準庫 + PyYAML,不需要外部 embedding API 或向量資料庫。兩條 pipeline 的邏輯都在 scripts/ch22_retrieval_benchmark.py 裡,約 450 行,可以直接讀。

如果你想理解 entity-aware retrieval 的機制(而不是規模),examples/ch22-retrieval-benchmark/ 裡有一份 8-chunk 的小型 benchmark,entity 連結和 graph fan-out 的過程在那個規模下比較容易 trace。


這一章在整個系列裡,真正補上的東西是什麼?

如果說:

CH22 補上的其實是另一種同樣重要的東西:

evidence

也就是:

這整套系統不是只是很會長功能,而是真的在最基本的 retrieval 問題上,給出比 pure lexical baseline 更好的答案。

這個 evidence 不是完美的。Q5 只贏了一個位置,Q3 的差距只在排序而非 cluster,Q2/Q6 的 baseline 是壞掉的才輸。

但 Q1 的勝利是乾淨的:baseline 跑向了錯誤的 cluster,entity graph traversal 找到了對的概念區域。這一個 case,已經足夠說明 entity-aware retrieval 和 pure text similarity 在做的是本質上不同的事情。

我覺得這一章很重要,因為它第一次把整個系列從:

往前推成:

這個轉變一旦成立,後面的 planner、critique、OpenClaw integration,才不會看起來像過度工程。

因為讀者心裡會有一個很清楚的 anchor:

原來 CtxFST 不是只是把 RAG 變複雜,而是真的讓 retrieval 在一個非常常見的場景裡更像在理解知識,而不只是比對文字。

Q1 那個場景非常值得記住:使用者用「用 AI 做語意搜尋」這個上位概念去搜自己的筆記。這不是邊緣案例,這是任何個人知識庫最典型的使用方式——沒有人搜自己的筆記時會精確記得每篇文章的標題用詞。Pure text similarity 在這裡的失敗不是 tokenizer 的問題,是它根本就在回答一個不同的問題。

Entity graph 回答的是:這個概念在你的知識圖裡住在哪裡,周圍連著什麼。

這才是整個系列真正站穩的地方。