構建 Human-friendly RAG:六大 LLM 在結構化知識提取上的表現評測
前言:當 RAG 遇上知識圖譜
在構建個人知識庫(如 Logseq)或開發 Human-friendly RAG(檢索增強生成)系統時,我們面臨的最大挑戰往往不是「獲取信息」,而是「信息的結構化」與「精準度」。
最近,我進行了一項實驗:讓六大主流 AI 模型(Claude Opus 4.5, ChatGPT 5.1, Qwen 3 Max, Doubao 1.6, Grok 4.1, Gemini 3.0 Pro)針對 React 中最難懂的概念之一 —— useEffect,生成適合 Logseq 的結構化知識卡片。
實驗結果令人驚訝,雖然各模型都能生成正確代碼,但在 「構建心智模型」 和 「知識結構化」 的能力上,差距顯著。
冠軍揭曉:為什麼 Claude Opus 4.5 是最佳選擇? (9.5/10)
在所有模型中,Claude 展現了「資深架構師」級別的思維。它不僅僅是在解釋語法,而是在重塑我們對技術的理解。
1. 定義的精準度(Precision)
這是 Claude 拉開差距的關鍵。看看它對 useEffect 本質的描述:
Claude: "useEffect 的本質不是 lifecycle hook,而是 「響應式資料管線」 ,用來同步外部系統與 React 狀態。"
其他模型: 多半停留在 "類似於 componentDidMount" 或 "處理副作用" 的層面。
這種表述對於 RAG 系統至關重要,因為它減少了語義檢索時的模糊匹配,提供了極高的解釋性(Explainability)。
2. 重塑心智模型(Mental Model)
Claude 對於 Dependency Array 的解釋令人拍案叫絕:
- 常見誤區: 它是一個「優化性能」的工具。
- Claude 的定義: 它不是「優化」工具,而是 「正確性」工具 。
這種深刻的洞察,能直接幫助學習者建立正確的思維路徑,這是構建高質量知識圖譜的核心。
3. 完美的教學結構
Claude 的輸出自帶邏輯層次:基礎概念 → 反模式 → 解決方案 → 高級類比(同步器)。這幾乎可以直接轉化為 Logseq 中的層級結構,無需人工二次整理。
選手綜合評測:各模型的定位與特長
雖然 Claude 奪冠,但其他模型在特定場景下也有不可替代的優勢。以下是詳細的橫向對比:
| 模型 | 定位 | 評分 | 特點 | 適用場景 |
|---|---|---|---|---|
| Claude | 架構師 | 9.5 | 深度理解、結構嚴謹、教學設計強 | 深度學習、架構設計、建立知識骨架 |
| ChatGPT | 實戰專家 | 9.0 | 場景豐富、覆蓋面廣、細節多 | 解決具體 Bug、代碼審查、補充實例 |
| Qwen | 創新思考者 | 8.5 | 獨特的 "Gap" 分析、視角新穎 | 尋找技術盲點、啟發新思路 |
| Doubao | 實用主義者 | 8.0 | 接地氣、中文自然 | 快速入門、團隊基礎文檔 |
| Grok | 精煉實用派 | 8.0 | 極度簡潔、直擊重點 | 快速備忘、Cheatsheet |
| Gemini | 基礎概括者 | 7.0 | 核心準確、中規中矩 | 概念速記 |
- ChatGPT (The Veteran): 雖然在概念深度上略遜 Claude,但它提供了最豐富的「實戰場景」和「錯誤案例」,是解決具體工程問題的好幫手。
- Qwen (The Innovator): 令人驚喜的是 Qwen 提出了 "Educational Gap"(教育落差)的概念,指出了開發者直覺與 React 模型之間的斷層,這是一個非常有價值的獨特視角。
實戰建議:如何構建完美的 RAG 知識庫
基於這次評測,對於想要在 Logseq 中構建高質量知識庫的朋友,我推薦採用 「混合策略」 :
Step 1: 用 Claude 搭建骨架
利用 Claude 極強的結構化能力,建立知識圖譜的核心層級。
useEffect 知識圖譜/
├── 核心概念 (Claude)
│ └── 響應式資料管線
├── 心智模型 (Claude)
│ └── 同步器類比
...
Step 2: 用 ChatGPT 填充血肉
將 ChatGPT 提供的豐富實戰場景(Edge cases)和常見錯誤(Stale Closure 具體案例)掛載到相應節點下。
Step 3: 用 Qwen 尋找盲點
參考 Qwen 的 "Gap" 分析,在筆記中標註「為什麼這裡容易出錯」,增加知識的深度。
總結
在 AI 輔助學習和知識管理的時代,模型的選擇決定了知識的質量。
如果你追求的是 「快速解決問題」 ,ChatGPT 依然是王者;但如果你追求的是 「深刻理解本質」 並希望構建一個可長期復用的 結構化知識庫 ,Claude 目前是當之無愧的最佳架構師。
附註一:
Claude Opus 4.5 生成的知識點
[
{
"id": "kp_01",
"type": "Concept",
"topic": "useEffect 的本質是響應式資料管線",
"core_statement": "useEffect 的本質不是 lifecycle hook,而是「響應式資料管線」,用來同步外部系統與 React 狀態。",
"implication": "從 class component 轉換過來的開發者需要轉換心智模型,不應套用舊的生命週期思維。",
"context_scenario": "在設計 useEffect 邏輯時",
"tags": ["React", "useEffect", "mental-model", "reactive-programming"]
},
{
"id": "kp_02",
"type": "Concept",
"topic": "Stale Closure 閉包過期問題",
"core_statement": "Stale closure 指 effect 內的變數因為閉包特性,永遠保留著舊值而非最新狀態。",
"implication": "JavaScript 閉包會「捕獲」當下的變數值,若 effect 不重新執行,變數就不會更新。",
"context_scenario": "當 effect 內使用了 state 或 props 但未加入 dependency array 時",
"tags": ["React", "useEffect", "closure", "JavaScript", "bug"]
},
{
"id": "kp_03",
"type": "Anti-Pattern",
"topic": "故意省略 dependency 以避免重複執行",
"core_statement": "為了避免 effect 重複執行而故意省略 dependency array 中的依賴項,會導致 stale closure 問題。",
"implication": "正確解法是用 useCallback、useMemo 或重構邏輯,而非隱藏依賴。",
"context_scenario": "在 useEffect 的 dependency array 設計時",
"tags": ["React", "useEffect", "anti-pattern", "dependency-array"]
},
{
"id": "kp_04",
"type": "Anti-Pattern",
"topic": "單一 effect 塞入過多邏輯",
"core_statement": "把所有副作用邏輯塞進同一個 useEffect 中,會讓程式難以維護且違反單一職責原則。",
"implication": "混雜的 effect 難以追蹤哪個依賴觸發了哪段邏輯,也難以獨立測試。",
"context_scenario": "當一個 component 有多種不同類型的副作用時",
"tags": ["React", "useEffect", "anti-pattern", "single-responsibility"]
},
{
"id": "kp_05",
"type": "Procedure",
"topic": "依邏輯單位拆分多個 effect",
"core_statement": "應依照「邏輯單位」將副作用拆分為多個獨立的 useEffect,每個 effect 只負責一件事。",
"implication": "拆分後每個 effect 有獨立的 dependency array,更容易推理和維護。",
"context_scenario": "在設計 React component 的副作用架構時",
"tags": ["React", "useEffect", "best-practice", "separation-of-concerns"]
},
{
"id": "kp_06",
"type": "Example",
"topic": "Effect 拆分範例:資料讀取與事件訂閱",
"core_statement": "一個 effect 專門處理資料讀取(fetch data),另一個 effect 專門處理事件訂閱(event subscription),兩者分開管理。",
"implication": "資料讀取通常依賴 ID 或 query 參數,事件訂閱通常依賴 handler function,兩者生命週期不同。",
"context_scenario": "在需要同時 fetch API 又要監聽 WebSocket 或 DOM 事件的 component",
"tags": [
"React",
"useEffect",
"example",
"data-fetching",
"event-subscription"
]
},
{
"id": "kp_07",
"type": "Anti-Pattern",
"topic": "用 useEffect 模擬 componentDidMount",
"core_statement": "不應該用 useEffect 來模擬 class component 的 componentDidMount 生命週期方法。",
"implication": "這種思維會讓開發者錯誤理解 useEffect 的用途,導致設計出有問題的副作用邏輯。",
"context_scenario": "從 class component 遷移到 function component 時",
"tags": ["React", "useEffect", "anti-pattern", "lifecycle", "migration"]
},
{
"id": "kp_08",
"type": "Procedure",
"topic": "使用空 dependency array 實現初始化執行",
"core_statement": "若只想在 component 初始化時執行一次邏輯,使用空的 dependency array [],但必須確保 effect 內沒有任何會變動的值。",
"implication": "如果 effect 內用到會變動的值卻給空 array,就會產生 stale closure。",
"context_scenario": "在需要一次性初始化(如 analytics tracking、第三方 SDK 初始化)時",
"tags": ["React", "useEffect", "dependency-array", "initialization"]
},
{
"id": "kp_09",
"type": "Concept",
"topic": "副作用應盡量純粹",
"core_statement": "useEffect 中的副作用應該盡量「純粹」,即可預測、可清理、無意外的外部影響。",
"implication": "純粹的副作用更容易測試、debug,也更容易被 React 的 Strict Mode 檢測出問題。",
"context_scenario": "在撰寫任何 useEffect 邏輯時",
"tags": ["React", "useEffect", "purity", "best-practice"]
},
{
"id": "kp_10",
"type": "Anti-Pattern",
"topic": "Fetch API 未處理 race condition",
"core_statement": "在 useEffect 中使用 fetch API 時若未處理 race condition,當使用者快速切換頁面會造成舊請求覆蓋新請求或 memory leak。",
"implication": "非同步操作的結果回來時,component 可能已經 unmount 或依賴已經改變。",
"context_scenario": "在 useEffect 內進行 API 請求時",
"tags": ["React", "useEffect", "fetch", "race-condition", "async"]
},
{
"id": "kp_11",
"type": "Procedure",
"topic": "使用 AbortController 解決 race condition",
"core_statement": "透過 AbortController 在 useEffect 的 cleanup function 中取消進行中的 fetch 請求,以解決 race condition 和 memory leak 問題。",
"implication": "AbortController 是 Web API 標準的一部分,可搭配 fetch 的 signal 選項使用。",
"context_scenario": "在 useEffect cleanup 中處理 fetch 請求取消",
"tags": ["React", "useEffect", "AbortController", "cleanup", "fetch"]
},
{
"id": "kp_12",
"type": "Anti-Pattern",
"topic": "Effect 內大量使用 isMounted 布林開關",
"core_statement": "若發現自己在 effect 內大量使用布林開關(如 isMounted)來控制邏輯,代表程式的資料流設計有根本問題。",
"implication": "布林開關是治標不治本的 patch,真正的問題在於副作用邏輯沒有正確對應到資料流。",
"context_scenario": "當開發者試圖用 flag 變數來「修復」 useEffect 的執行問題時",
"tags": ["React", "useEffect", "anti-pattern", "code-smell", "data-flow"]
},
{
"id": "kp_13",
"type": "Procedure",
"topic": "重新思考資料流而非繼續 patch",
"core_statement": "當 useEffect 出現複雜的控制邏輯時,應該退一步重新思考整體資料流設計,而不是繼續用 workaround 修補。",
"implication": "好的 React 設計讓資料流向清晰,副作用自然簡單;複雜的 effect 通常是設計問題的症狀。",
"context_scenario": "當 useEffect 變得難以理解或維護時",
"tags": ["React", "useEffect", "refactoring", "data-flow", "architecture"]
},
{
"id": "kp_14",
"type": "Concept",
"topic": "Dependency array 的核心作用",
"core_statement": "useEffect 的 dependency array 告訴 React 何時需要重新執行 effect,以確保 effect 內的值與最新狀態同步。",
"implication": "dependency array 不是「優化」工具,而是「正確性」工具。",
"context_scenario": "在理解 useEffect 的基本機制時",
"tags": ["React", "useEffect", "dependency-array", "fundamentals"]
},
{
"id": "kp_15",
"type": "Analogy",
"topic": "Effect 如同外部系統的同步器",
"core_statement": "useEffect 就像一個「同步器」,負責讓 React 的狀態與外部系統(DOM、API、訂閱)保持一致。",
"implication": "這個類比幫助理解為何 effect 需要 cleanup:同步器在狀態改變時需要先解除舊的同步再建立新的。",
"context_scenario": "在向他人解釋 useEffect 的設計哲學時",
"tags": ["React", "useEffect", "analogy", "synchronization", "mental-model"]
}
]
附註二:
ChatGPT 5.1 生成的知識點
[
{
"id": "kp_01",
"type": "Concept",
"topic": "useEffect power and risk",
"core_statement": "React 的 useEffect Hook 是一個非常強大的功能,但也同時非常容易被錯誤使用。",
"implication": "強大抽象通常也有高誤用成本,需要更嚴謹的使用模式。",
"context_scenario": "在使用 React 函式元件處理副作用邏輯時",
"tags": ["react", "useEffect", "side-effects", "frontend"]
},
{
"id": "kp_02",
"type": "Concept",
"topic": "dependency array 重要性",
"core_statement": "useEffect 的 dependency array 是用來告訴 React:哪些值變動時需要重新執行 effect。",
"implication": "正確填寫 dependency array 是避免錯誤與 stale closure 的關鍵。",
"context_scenario": "在撰寫 useEffect 並決定其依賴值時",
"tags": ["react", "useEffect", "dependency-array"]
},
{
"id": "kp_03",
"type": "Anti-Pattern",
"topic": "刻意省略依賴",
"core_statement": "許多開發者會因為擔心 useEffect 重複執行,而刻意在 dependency array 中省略實際使用到的依賴。",
"implication": "為了避免重新執行而省略依賴會製造隱藏 bug,而不是解決性能問題。",
"context_scenario": "在優化 useEffect 執行次數時錯誤地移除依賴",
"tags": ["react", "useEffect", "anti-pattern", "dependency-array"]
},
{
"id": "kp_04",
"type": "Concept",
"topic": "stale closure 問題",
"core_statement": "當 useEffect 的 dependency array 缺少必要依賴時,effect 內的閉包會持續捕捉舊值,導致 stale closure(閉包過期)問題。",
"implication": "stale closure 會讓 state 或 props 看起來無法更新,導致難以追蹤的邏輯錯誤。",
"context_scenario": "在 effect 內使用 state 或 props 且未正確列入 dependency array 時",
"tags": ["react", "useEffect", "closure", "bug"]
},
{
"id": "kp_05",
"type": "Concept",
"topic": "stale closure 效果",
"core_statement": "stale closure 會讓 useEffect 中使用的變數永遠停留在舊值,而不會隨著最新 state 或 props 更新。",
"implication": "畫面看似渲染正確但邏輯依舊用舊值,會造成難以察覺的行為異常。",
"context_scenario": "在 effect 中執行計算、API 呼叫或註冊 handler 並依賴 state 值時",
"tags": ["react", "bug", "closure", "state"]
},
{
"id": "kp_06",
"type": "Procedure",
"topic": "依照邏輯單位拆分 effect",
"core_statement": "正確使用 useEffect 的方式,是依照「邏輯單位」拆分成多個 effect,而不是把所有邏輯塞進同一個 effect。",
"implication": "拆分 effect 可以提升可讀性、可測試性,並避免彼此依賴混在一起。",
"context_scenario": "當一個元件內有多種不同的副作用邏輯需要處理時",
"tags": ["react", "useEffect", "design", "refactoring"]
},
{
"id": "kp_07",
"type": "Example",
"topic": "effect 拆分示例",
"core_statement": "例如可以用一個 useEffect 專門處理資料讀取(data fetching),另一個 useEffect 專門處理事件訂閱(event subscription)。",
"implication": "將不同 concern 拆分成不同 effect 可以更清楚描述資料流與生命週期。",
"context_scenario": "在同一個元件中既要抓取遠端資料,又要訂閱 window 或 DOM 事件時",
"tags": ["react", "example", "data-fetching", "events"]
},
{
"id": "kp_08",
"type": "Concept",
"topic": "useEffect 的本質",
"core_statement": "useEffect 的本質並不是傳統意義上的 lifecycle,而是「響應式資料管線」。",
"implication": "開發者應從資料依賴與反應式更新的角度思考 useEffect,而不是從 class lifecycle 的角度移植思維。",
"context_scenario": "在從 class component 過渡到 function component 時設計副作用邏輯",
"tags": ["react", "useEffect", "reactive", "mental-model"]
},
{
"id": "kp_09",
"type": "Anti-Pattern",
"topic": "用 useEffect 模擬 componentDidMount",
"core_statement": "不應該使用 useEffect 來模擬 componentDidMount 這類 class lifecycle 方法。",
"implication": "強行模擬舊 lifecycle 會阻礙採用 React Hooks 的正確心智模型並導致錯誤依賴管理。",
"context_scenario": "從 class component 遷移邏輯時,試圖一比一對應到 useEffect 寫法",
"tags": ["react", "anti-pattern", "lifecycle", "hooks-migration"]
},
{
"id": "kp_10",
"type": "Concept",
"topic": "空 dependency array 的語意",
"core_statement": "若只想在初始化時執行一次邏輯,可以使用空 dependency array,表示 effect 只在初次 render 後執行一次。",
"implication": "空 dependency array 是一種強烈保證,代表 effect 不會因任何值變動而重新執行。",
"context_scenario": "在元件掛載時僅需執行一次的設定、資料預載或初始化邏輯",
"tags": ["react", "useEffect", "dependency-array", "initialization"]
},
{
"id": "kp_11",
"type": "Anti-Pattern",
"topic": "初始化 effect 內含變動值",
"core_statement": "當使用空 dependency array 時,effect 內不應該包含會逐漸變動的值,否則會造成邏輯與實際執行次數不一致。",
"implication": "在空 dependency array 中使用變動值等同於把 stale closure 問題變成設計特性。",
"context_scenario": "在初始化 effect 中使用日後會更新的 props 或 state 卻未重新執行 effect",
"tags": ["react", "anti-pattern", "closure", "dependency-array"]
},
{
"id": "kp_12",
"type": "Concept",
"topic": "副作用應盡量純粹",
"core_statement": "useEffect 中的副作用邏輯應該盡量純粹,避免不必要的外部依賴與隱藏狀態。",
"implication": "純粹的副作用較容易推理、重構與測試,也較不容易產生競態或記憶體洩漏。",
"context_scenario": "在設計資料抓取、事件訂閱、DOM 操作等副作用邏輯時",
"tags": ["react", "side-effects", "purity", "design"]
},
{
"id": "kp_13",
"type": "Concept",
"topic": "fetch 競態條件風險",
"core_statement": "使用 fetch API 時需要處理 race condition,以避免使用者快速切換頁面或狀態時造成錯誤更新或 memory leak。",
"implication": "未處理競態可能導致已卸載的元件仍嘗試 setState 或處理過期回應。",
"context_scenario": "在 useEffect 中進行非同步資料抓取並依結果更新 state 時",
"tags": ["react", "fetch", "race-condition", "memory-leak"]
},
{
"id": "kp_14",
"type": "Procedure",
"topic": "用 AbortController 處理 fetch",
"core_statement": "處理 fetch API 競態與避免 memory leak 的常見做法,是在 useEffect 中使用 AbortController 來中止不再需要的請求。",
"implication": "中止過期請求可避免不必要的網路成本與錯誤 UI 更新。",
"context_scenario": "在使用 useEffect 發送 fetch 請求且元件可能在請求完成前卸載或變更條件時",
"tags": ["react", "fetch", "AbortController", "cleanup"]
},
{
"id": "kp_15",
"type": "Anti-Pattern",
"topic": "在 effect 中大量使用布林開關",
"core_statement": "如果在 useEffect 內大量使用布林開關(例如 isMounted)來控制流程,代表程式邏輯設計存在問題。",
"implication": "過多布林旗標通常是資料流設計不良的氣味,而不是應該被更多 patch 掩蓋的問題。",
"context_scenario": "在 effect 中透過多個布林 state 來避免重複呼叫或判斷是否已初始化時",
"tags": ["react", "anti-pattern", "code-smell", "state-management"]
},
{
"id": "kp_16",
"type": "Procedure",
"topic": "發現布林氣味後的行動",
"core_statement": "當發現自己在 useEffect 中依賴大量布林開關時,應該重新思考與重構資料流,而不是繼續增加更多條件判斷來 patch。",
"implication": "重新設計資料流通常能從根本上消除競態、重複執行與初始化判斷問題。",
"context_scenario": "維護一段 useEffect 內部邏輯變得複雜且充滿 isXXX flag 時",
"tags": ["react", "refactoring", "architecture", "state-management"]
}
]
附註三:
測試用的 System Prompt
You are an expert Knowledge Engineer and card-designer for spaced repetition.
Your job:
Given an article or note, decompose it into **atomic Knowledge Points (KPs)** and output them as a **JSON array**.
Do NOT summarize the text. Instead, extract individual, reusable knowledge units.
---
## CORE RULES
1. **Atomicity**
- One KP = one main idea.
- Do NOT mix multiple ideas into one KP.
2. **Self-contained**
- Each KP must be understandable without seeing the original text.
- Avoid pronouns like "this", "it", "they". Replace them with explicit nouns.
3. **Full spectrum of knowledge**
- Do NOT only extract definitions and facts.
- Always look for procedures, pitfalls, examples, analogies, and open questions.
4. **Max count**
- Extract up to 30 KPs, focusing on the most useful ones for learning and thinking.
---
## KNOWLEDGE TYPE CLASSIFICATION
For each KP, choose exactly ONE `type` from:
1. **Concept** – Definitions, facts, properties. (“What is it?”)
2. **Procedure** – Step-by-step methods, algorithms, workflows. (“How to do it?”)
3. **Anti-Pattern** – Common mistakes, traps, things to avoid. (“What goes wrong?”)
4. **Analogy** – Metaphors, comparisons to other domains. (“X is like Y because…”)
5. **Example** – Concrete cases, scenarios, code examples, historical events. (“For example…”)
6. **Gap** – Limitations, open problems, future work, unknowns. (“We still don’t know…”)
---
## INFERENCE RULES (IMPLICIT KNOWLEDGE)
Besides what is explicitly written, infer short implications when they are **obvious to a domain expert**:
- If the text describes a **solution**, the hidden KP may be the **problem** it solves.
- If the text describes a **benefit**, the hidden KP may be the **trade-off or cost**.
- If a specific tool/term is used, infer its broader **category or role** when helpful.
- If nothing meaningful can be inferred, use an empty string "" for `implication`.
---
## OUTPUT FORMAT (JSON ONLY)
Return ONLY valid JSON. No explanations, no comments.
Each KP must have this structure:
[
{
"id": "kp_01",
"type": "Concept | Procedure | Anti-Pattern | Analogy | Example | Gap",
"topic": "Short 2-6 word title of the idea",
"core_statement": "One atomic statement capturing the essence of this KP.",
"implication": "Very short hidden context, trade-off, or 'why this matters'. Empty string if none.",
"context_scenario": "Where or when this KP applies (e.g., 'In React useEffect hooks'). Empty string if not needed.",
"tags": ["tag1", "tag2", "tag3"]
}
]
- Use English for `type` and `tags`.
- `core_statement` and `context_scenario` can be in the same language as the input text.
- If some fields are not applicable, set them to an empty string "" (except `id`, `type`, `topic`, `core_statement` which are required).
附註四:
測試用 Sample Artcle
React 的 useEffect Hook 是最強大的功能之一,但也經常被錯誤使用。最常見的問題出現在 dependency array。許多開發者會因為擔心 effect 重複執行,而故意省略依賴,導致 stale closure(閉包過期)問題。這會讓 effect 裡的變數永遠使用舊值。
另一個常見錯誤是把所有邏輯塞進同一個 effect 中。正確的做法是依照「邏輯單位」拆分多個 effect。例如:一個 effect 處理資料讀取,另一個 effect 處理事件訂閱。
useEffect 的本質其實不是 lifecycle,而是「響應式資料管線」。所以不要用它模擬 componentDidMount。若你只想在初始化時執行一次邏輯,應該用空 dependency array,但要確保 effect 裡沒有任何逐漸變動的值。
此外,副作用應該盡量純粹。例如:fetch API 要處理 race condition,以避免使用者快速切換頁面時造成 memory leak。這通常透過 AbortController 解決。
最後,若你發現自己在 effect 內大量使用布林開關(e.g., isMounted),那代表你的程式邏輯設計有問題,應該重新思考資料流,而不是繼續 patch。