Obsidian MCP + 混合檢索:2026 參考指南
# 使用官方 Obsidian 文件了解應用程式基礎;使用 Blake's 參考指南了解 MCP、混合搜尋,以及為包含 16,894 個檔案的 AI vault 建立索引。
重點整理
情境工程,而非筆記整理。 Obsidian vault 對 AI 的價值不在筆記本身,而在於讓筆記可供查詢的檢索層。一個擁有 16,000 個檔案卻沒有檢索機制的 vault,只是一個唯寫資料庫;一個擁有 200 個檔案、具備 hybrid 搜尋與 MCP 整合的 vault,才是真正的 AI 知識庫。檢索基礎架構才是產品本體,筆記只是原料。
Hybrid 檢索勝過純關鍵字或純語意搜尋。 BM25 能捕捉精確的識別碼與函式名稱;向量搜尋則能跨越不同用語,捕捉同義詞與概念性匹配。Reciprocal Rank Fusion(RRF)能在無需分數校準的情況下合併兩者。任一方法單獨使用,都無法涵蓋兩種失敗模式。MS MARCO 段落排序的研究證實了此模式:hybrid 檢索持續優於任一方法的單獨表現。3 hybrid retriever 深度剖析涵蓋 RRF 的數學原理、真實數字的實例演算、失敗模式分析,以及互動式融合計算機。
MCP 讓 AI 工具直接存取 vault。 Model Context Protocol(MCP)伺服器將檢索器暴露為一項工具,讓 Claude Code、Codex CLI、Cursor 等 AI 工具可以直接呼叫。代理查詢 vault,收到帶有來源標註的排序結果,然後在不載入整個檔案的情況下使用這些情境。MCP 伺服器只是檢索引擎的一層薄封裝。
Local-first 意味著零 API 成本與完整隱私。 整個堆疊都在單一機器上執行:SQLite 負責儲存、Model2Vec 產生 embeddings、FTS5 處理關鍵字搜尋、sqlite-vec 執行向量 KNN。沒有雲端服務、沒有 API 呼叫、沒有網路依賴。個人筆記永遠不會離開您的機器。以 OpenAI API 價格計算,對 49,746 個 chunks 進行完整重新嵌入約需 0.30 美元,但真正的成本是延遲、隱私暴露,以及一個本應離線運作的系統卻產生網路依賴。4
增量索引讓系統在 10 秒內保持最新。 透過檔案修改時間比對偵測變更,只有修改過的檔案才會重新 chunk 與重新嵌入。在 Apple M 系列硬體上,完整重建索引約需四分鐘;一般日常編輯的增量更新則可在十秒內完成。系統無需人工介入便能保持最新狀態。
此架構可從 200 篇筆記擴展到 20,000 篇以上。 相同的三層設計(輸入、檢索、整合)在任何 vault 規模下都能運作。可從小型 vault 上只使用 BM25 搜尋開始;當關鍵字衝突成為問題時,再加入向量搜尋;當需要同時兼顧精確與語意匹配時,再加入 RRF 融合。每一層都能獨立發揮作用,也能獨立移除。
如何使用本指南
本指南涵蓋完整系統。您的起點取決於目前的狀況:
| 您的身份是… | 從這裡開始 | 接著探索 |
|---|---|---|
| Obsidian + AI 新手 | 為何選擇 Obsidian 作為 AI 基礎架構、快速上手 | Vault 架構、MCP 伺服器架構 |
| 已有 vault,想加入 AI 存取 | MCP 伺服器架構、Claude Code 整合 | Embedding 模型、全文搜尋 |
| 正在建置檢索系統 | 完整檢索管線、Reciprocal Rank Fusion | 效能調校、疑難排解 |
| 團隊或企業情境 | 決策框架、知識圖譜模式 | 開發者工作流程範例、遷移指南 |
標記為 Contract 的章節包含實作細節、設定區塊與失敗模式;標記為 Narrative 的章節著重於概念、架構決策及設計選擇背後的思考;標記為 Recipe 的章節則提供逐步工作流程。
為何選擇 Obsidian 作為 AI 基礎架構
本指南的核心論點:Obsidian vault 是個人 AI 知識庫的最佳基礎,因為它具備 local-first、純文字、圖形結構的特性,而且使用者掌控堆疊中的每一層。
Obsidian 提供給 AI 的優勢,是其他替代方案所欠缺的
純文字 markdown 檔案。 每一則筆記都是檔案系統上的 .md 檔案。沒有專有格式、無需資料庫匯出、不需 API 就能讀取內容。任何能讀取檔案的工具,都能讀取您的 vault。grep、ripgrep、Python 的 pathlib、SQLite FTS5——全都可以直接在原始檔案上運作。當您建置檢索系統時,索引的是檔案,而非 API 回應。索引與來源永遠一致,因為來源就是檔案系統本身。
Local-first 架構。 Vault 就在您的機器上。沒有伺服器、沒有雲端同步依賴、沒有 API 速率限制、沒有約束您如何處理自己內容的服務條款。您可以在沒有任何外部服務的情況下,對筆記進行嵌入、索引、chunk 與搜尋。這對 AI 基礎架構至關重要,因為檢索管線的速度取決於磁碟效能,而非 API 端點的回應速度。這對隱私也同樣重要:包含憑證、健康資料、財務資訊與私人思緒的個人筆記,永遠不會離開您的機器。
透過 wiki-link 形成的圖形結構。 Obsidian 的 [[wiki-link]] 語法在筆記之間建立了有向圖。一則關於 OAuth 實作的筆記,會連結至 token 輪替、session 管理、API 安全的筆記。圖形結構編碼了人工策劃的概念間關係。向量 embeddings 捕捉的是語意相似度,但 wiki-link 捕捉的是作者思考該主題時刻意建立的連結。這種訊號是 embeddings 無法複製的。
豐富的外掛生態系。 Obsidian 擁有超過 2,500 個社群外掛(截至 2026 年 3 月,較 2025 年中的 1,800 多個成長)。Dataview 讓您像查詢資料庫一樣查詢 vault;Templater 以 JavaScript 邏輯從範本產生筆記;Git 整合將 vault 同步至儲存庫;Linter 強制格式一致性。Bases 核心外掛(於 v1.9.10 推出)以 frontmatter 屬性作為欄位,為 vault 檔案加上類資料庫的檢視——表格、圖庫、行事曆與看板——並儲存為 .base 檔案。27 這些外掛為 vault 加上結構,同時不改變底層的純文字格式。檢索系統索引的是這些外掛的輸出,而非外掛本身。
超過 500 萬使用者。 Obsidian 擁有龐大的活躍社群,持續產出範本、工作流程、外掛與文件。當您在 vault 組織或外掛設定上遇到問題時,通常都有人記錄過解決方案。社群也產出許多 Obsidian 周邊工具:MCP 伺服器、索引腳本、發佈管線與 API 封裝工具。
單純的檔案系統所無法提供的
一個裝滿 markdown 檔案的目錄,擁有純文字的優勢,但缺少 Obsidian 所提供的三項關鍵能力:
-
雙向連結。 Obsidian 會自動追蹤 backlinks。當您從筆記 A 連結到筆記 B 時,筆記 B 會顯示筆記 A 引用了它。圖形面板可視覺化連結叢集。這種雙向感知是原始檔案系統無法提供的中繼資料。
-
具備外掛渲染的即時預覽。 Dataview 查詢、Mermaid 圖表與 callout 區塊皆為即時渲染。寫作體驗比文字編輯器更豐富,但儲存格式仍為純文字。您在豐富的環境中書寫與組織;檢索系統則索引原始的 markdown。
-
社群基礎設施。 外掛探索、主題市集、同步服務(選用)、發佈服務(選用),以及文件生態系。您可以用獨立工具複製任何單一功能,但 Obsidian 將它們整合成一致的工作流程。
Obsidian 不會做的事(以及您需要自行建置的部分)
Obsidian 並不包含檢索基礎架構。它具備基本搜尋(全文、檔名、標籤),但沒有嵌入管線、沒有向量搜尋、沒有融合排序、沒有 MCP 伺服器、沒有憑證過濾、沒有 chunking 策略,也沒有與外部 AI 工具的整合掛鉤。本指南涵蓋的,正是您在 Obsidian 之上建置的基礎架構。 Vault 是基礎,檢索管線、MCP 伺服器與整合掛鉤才是基礎架構。
此處描述的架構是 markdown-first,而非 Obsidian 專屬。 若您使用 Logseq、Foam、Dendron,或僅是一個裝滿 markdown 檔案的純粹目錄,此檢索管線也能以完全相同的方式運作。Chunker 讀取 .md 檔案,Embedder 處理文字字串,Indexer 寫入 SQLite。這些元件皆不依賴 Obsidian 特有的功能。Obsidian 的貢獻,在於它提供了一個能產出 markdown 檔案的書寫與組織環境,供檢索器索引之用。
快速入門:首個 AI 連接的 vault
本節將在五分鐘內把 vault 連接至 AI 工具。您將安裝 Obsidian、建立 vault、安裝 MCP server,並執行首次查詢。快速入門使用社群 MCP server 以立即取得成果。後續章節則介紹如何為生產環境建置自訂檢索管線。
先決條件
- macOS、Linux 或 Windows
- Node.js 18+(用於 MCP server)
- Obsidian 1.12+(用於 CLI 整合;較早版本適用於僅 MCP 的設定)
- 已安裝 Claude Code、Codex CLI 或 Cursor
步驟 1:建立 vault
從 obsidian.md 下載 Obsidian 並建立新的 vault。選擇一個您記得住的位置——MCP server 需要絕對路徑。
# Example vault location
~/Documents/knowledge-base/
新增一些筆記,讓檢索器有內容可處理。即使只有 10 至 20 篇筆記也足以看到成果。每篇筆記應為 .md 檔案,具備有意義的標題與至少一段內容。
步驟 2:安裝 MCP server
多個社群 MCP server 可提供立即的 vault 存取能力。此生態系在 2025-2026 年間已顯著成長。近期值得關注的更新包括 MCPVault v0.11.0(2026 年 3 月),新增了 list_all_tags 以掃描 frontmatter 與 hashtag 並計數、改善了點狀資料夾處理,並支援 .base 與 .canvas 檔案。25 該套件在 npm 上也更名為 @bitbonsai/mcpvault。
2026 年 4 月的轉變——Obsidian CLI 成為首選橋接方式: Obsidian 1.12.7(2026 年 3 月 23 日)推出了一流的 CLI,具備獨立二進位檔、TUI 與 socket 檔案。社群工具正積極從 Local REST API plugin(驅動
mcp-obsidian的元件)遷移至基於 CLI 的整合方式,因為後者更快速且更穩定。MarkusPfundstein/mcp-obsidianrepo 自 2025 年 6 月起未有任何提交,也從未有過已標記的發行版本——請將其視為維護模式,並優先採用基於 CLI 的 server 或下方列出的較新社群替代方案。32 請參閱本指南稍後「Obsidian CLI for AI Workflows」一節,了解建議的設定。
| Server | 作者 | 傳輸方式 | 需要 Plugin | 主要特色 |
|---|---|---|---|---|
| obsidian-mcp-server | StevenStavrakis | STDIO | 否 | 輕量、基於檔案 |
| mcp-obsidian | MarkusPfundstein | STDIO | Local REST API | 透過 REST 提供完整 vault CRUD——維護模式,自 2025 年 6 月起無提交32 |
| obsidian-mcp-tools | jacksteamdev | STDIO | 是(plugin) | 語意搜尋 + Templater |
| obsidian-claude-code-mcp | iansinnott | WebSocket | 是(plugin) | 針對 Claude Code 的自動探索 |
| obsidian-mcp-server | cyanheads | STDIO | Local REST API | 標籤、frontmatter 管理 |
| Hybrid Search MCP | community | STDIO | 否 | BM25 + 語意搜尋 MCP server + CLI。截至 2026 年 4 月為新推出且正積極維護中。 |
快速入門最簡單的選擇是直接讀取 .md 檔案的基於檔案的 server:
npm install -g obsidian-mcp-server
步驟 3:設定您的 AI 工具
Claude Code——新增至 ~/.claude/settings.json:
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-server",
"args": ["--vault", "/absolute/path/to/your/vault"]
}
}
}
Codex CLI——新增至 .codex/config.toml:
[mcp_servers.obsidian]
command = "obsidian-mcp-server"
args = ["--vault", "/absolute/path/to/your/vault"]
Cursor——新增至 .cursor/mcp.json:
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-server",
"args": ["--vault", "/absolute/path/to/your/vault"]
}
}
}
步驟 4:執行首次查詢
開啟您的 AI 工具,並提出一個 vault 筆記能夠回答的問題:
Search my Obsidian vault for notes about [topic you wrote about]
AI 工具會呼叫 MCP server,後者會搜尋您的 vault 並傳回相符的內容。您應當會看到包含檔案路徑與相關摘要的結果。
您剛才建構了什麼
您透過標準協定將本地知識庫連接至 AI 工具。MCP server 讀取您的 vault 檔案、執行基本搜尋並傳回結果。這是最小可行版本。
此快速入門並未提供的功能: - 混合檢索(BM25 + 向量搜尋 + RRF 融合) - 基於 embeddings 的語意搜尋 - 憑證過濾 - 增量索引 - 基於 hook 的自動情境注入
本指南其餘部分將說明如何建構上述每項能力。快速入門驗證了概念,完整管線則交付生產等級的檢索品質。
Obsidian CLI for AI Workflows
Obsidian 1.12(2026 年 2 月)引入了內建的命令列介面,為 AI 工作流程開啟了全新的整合介面。28 CLI 的角色是 Obsidian GUI 的遙控器——Obsidian 必須正在執行(或會在首次指令時自動啟動)。請於 Settings > General > Command line interface 中啟用。
CLI 對 AI 基礎設施的重要性
CLI 提供了對 Obsidian 原生操作的程式化存取,而這些操作以往僅能透過 GUI 或 plugin API 進行。就 AI 工作流程而言,關鍵能力如下:
- 從指令碼與 hook 執行搜尋。
obsidian search "query"與obsidian search:context "query"可從任何 shell 指令碼、hook 或自動化管線執行 vault 搜尋。search:context變體會回傳含有周圍上下文的相符行數,適合將結果餵入 AI 提示詞。 - 每日筆記自動化。
obsidian daily會開啟或建立當日的每日筆記。結合 shell 指令碼即可打造自動化每日簡報流程——hook 可將 AI 生成的摘要附加至每日筆記。 - 基於範本的筆記建立。
obsidian template list與obsidian template create可從 Templater 或核心範本產生筆記,讓 AI 代理能建立結構化的 vault 條目,而無需直接撰寫 markdown 檔案。 - 屬性管理。
obsidian property set與obsidian property get可讀取與寫入 frontmatter 屬性,讓指令碼無需解析 YAML 即可更新中繼資料。 - Plugin 控制。
obsidian plugin enable/disable/list可程式化管理 plugin,適合在批次操作時切換索引 plugin。 - 任務管理。
obsidian task list/add/complete提供結構化的任務存取介面,適合在 vault 中管理工作項目的 AI 代理使用。
CLI 與 MCP 在 AI 存取上的比較
CLI 與 MCP server 扮演不同角色,兩者互補而非競爭:
| 面向 | Obsidian CLI | MCP Server |
|---|---|---|
| 呼叫者 | Shell 指令碼、hook、cron 任務 | AI 代理(Claude Code、Codex、Cursor) |
| 協定 | POSIX 程序(stdin/stdout/stderr) | MCP(透過 STDIO 或 HTTP 的 JSON-RPC) |
| 強項 | Obsidian 原生操作(範本、plugin、屬性) | 自訂檢索(embeddings、BM25、RRF 融合) |
| 限制 | 無向量搜尋、無 embedding 管線 | 無法存取 Obsidian 內部操作 |
| 最適合 | 自動化指令碼、匯入管線、hook 動作 | 工作階段中即時的 AI 代理查詢 |
建議: 以 CLI 處理匯入自動化(建立筆記、管理屬性、執行 Obsidian 原生搜尋),並以 MCP 處理檢索(結合 embeddings 的 hybrid 搜尋)。PreToolUse hook 可先呼叫 obsidian search:context 作為快速預先檢查,再於需要排序結果時回退至完整的 MCP 檢索器。
範例:由 CLI 驅動的匯入 hook
#!/bin/bash
# Hook: append today's signals to daily note via CLI
DATE=$(date +%Y-%m-%d)
SUMMARY="$1"
obsidian daily # ensure daily note exists
obsidian file append "Daily Notes/${DATE}.md" "## AI Summary\n${SUMMARY}"
Obsidian Agent 外掛
有一類日益壯大的 Obsidian 外掛將 AI 編碼 agent 直接嵌入 vault 的使用者介面,提供了外部 MCP 伺服器設定之外的另一種選擇。這些外掛在 Obsidian 側邊欄內執行 AI agent,而非從外部工具進行連接。
Claudian
Claudian 將 Claude Code 作為 AI 協作者嵌入 vault 之中。vault 目錄成為 Claude 的工作目錄,賦予其完整的 agent 能力:檔案讀寫、搜尋、bash 命令以及多步驟工作流程。29
AI 基礎設施的關鍵功能:
- 情境感知提示。 自動附加目前聚焦的筆記,支援 @notename 檔案提及、基於標籤的排除,以及以編輯器選取內容作為情境。
- 視覺支援。 透過拖放、貼上或檔案路徑分析圖像,對於處理在 vault 中擷取的螢幕截圖與示意圖相當實用。
- 斜線命令。 建立可由 /command 觸發的可重用提示範本,以標準化 vault 操作。
- 權限模式。 YOLO(自動核准)、Safe(每個動作都需核准)與 Plan(僅規劃)模式,並配有安全封鎖清單與 vault 範圍限制。
Agent Client
Agent Client 透過 Agent Client Protocol(ACP)將 Claude Code、Codex CLI 與 Gemini CLI 整合至統一的 Obsidian 側邊欄。30
主要功能:
- 多 agent 切換。 在同一面板中與 Claude Code、Codex 或 Gemini CLI 對話,依需求切換不同的 agent。
- 筆記提及。 使用 @notename 將筆記內容納入提示,與 Claudian 類似但不綁定特定 agent。
- Shell 執行。 在對話中直接執行終端機命令——建置指令碼、git 命令或任何終端機操作,無須離開對話。
- 動作核准。 對檔案讀取、編輯與命令執行進行細緻控制。
何時使用 agent 外掛 vs 外部 MCP
| 情境 | Agent 外掛 | 外部 MCP |
|---|---|---|
| 以 AI 協助撰寫與編輯 vault 筆記 | 較佳——agent 可見編輯器情境 | 可行但無編輯器感知 |
| 跨多個 repo 的程式碼開發 | 有限——僅限 vault 範圍 | 較佳——以專案為範圍並具完整檔案系統存取 |
| 自大型已索引語料庫檢索 | 僅具基本搜尋 | 完整的 hybrid 檢索管線 |
| 筆記撰寫過程中的即時 vault 問答 | 理想——無須切換情境 | 需切換至終端機 |
建議: 以 vault 為中心的工作流程(撰寫、整理、摘要筆記)適合使用 agent 外掛。若開發工作流程需要 AI agent 具備完整的檢索管線,並存取 vault 之外的程式碼庫,則採用外部 MCP 伺服器。兩種方式可並存——在 Obsidian 內執行 Claudian 處理筆記工作,並於外部搭配 MCP 使用 Claude Code 進行開發。
決策框架:Obsidian 與替代方案
並非所有使用情境都需要 Obsidian。本節說明何時 Obsidian 是合適的基礎、何時屬於過度設計,以及何時其他方案更為合適。
決策樹
START: What is your primary content type?
│
├─ Structured data (tables, records, schemas)
│ → Use a database. SQLite, PostgreSQL, or a spreadsheet.
│ → Obsidian is for prose, not tabular data.
│
├─ Ephemeral context (current project, temporary notes)
│ → Use CLAUDE.md / AGENTS.md in the project repo.
│ → These travel with the code and reset per project.
│
├─ Team wiki (shared documentation, onboarding)
│ → Evaluate Notion, Confluence, or a shared git repo.
│ → Obsidian vaults are personal-first. Team sync is possible
│ but not native.
│
└─ Growing personal knowledge corpus
│
├─ < 50 notes
│ → A folder of markdown files + grep is sufficient.
│ → Obsidian adds value mainly through the link graph,
│ which needs density to be useful.
│
├─ 50 - 500 notes
│ → Obsidian adds value. Wiki-links create a navigable graph.
│ → BM25-only search (FTS5) is sufficient at this scale.
│ → Skip vector search and RRF until keyword collisions appear.
│
├─ 500 - 5,000 notes
│ → Full hybrid retrieval becomes valuable. Keyword collisions
│ increase. Semantic search catches queries that BM25 misses.
│ → Add vector search + RRF fusion at this scale.
│
└─ 5,000+ notes
→ Full pipeline is essential. BM25-only returns too much noise.
→ Credential filtering becomes critical (more notes = more
accidentally pasted secrets).
→ Incremental indexing matters (full reindex takes minutes).
→ MCP integration pays dividends on every AI interaction.
比較矩陣
| 標準 | Obsidian | Notion | Apple Notes | 純檔案系統 | CLAUDE.md |
|---|---|---|---|---|---|
| 本機優先 | 是 | 否(雲端) | 部分(iCloud) | 是 | 是 |
| 純文字 | 是(markdown) | 否(區塊) | 否(專有格式) | 是 | 是 |
| 圖譜結構 | 是(wiki-links) | 部分(提及) | 否 | 否 | 否 |
| AI 可索引 | 直接檔案存取 | 需 API | 需匯出 | 直接檔案存取 | 已在情境中 |
| 外掛生態系 | 2,500+ 個外掛 | 整合功能 | 無 | N/A | N/A |
| 離線支援 | 完整 | 僅快取唯讀 | 部分 | 完整 | 完整 |
| 可擴展至 10K+ 筆記 | 是 | 是(需 API) | 效能下降 | 是 | 否(單一檔案) |
| 費用 | 免費(核心) | 每月 $10 起 | 免費 | 免費 | 免費 |
何時 Obsidian 屬於過度設計
- 單一專案情境。 若 AI 僅需目前程式碼庫的情境,請將內容放入
CLAUDE.md、AGENTS.md或專案層級的文件。這些檔案會隨 repo 移動並自動載入。 - 結構化資料。 若內容為表格、記錄或結構描述,請使用資料庫。Obsidian 筆記以散文為主。Dataview 雖可查詢 frontmatter 欄位,但真正的資料庫更能處理結構化查詢。
- 臨時研究。 若筆記將在專案結束後捨棄,使用一個存放 markdown 檔案的暫存目錄更為簡潔。切勿為短暫內容建置檢索基礎設施。
何時 Obsidian 是正確選擇
- 跨越數月或數年累積知識。 價值會隨語料庫成長而複利累積。每日查詢長達六個月的 200 篇筆記 vault,其價值遠勝於僅查詢一次的 5,000 篇筆記 vault。
- 單一語料庫涵蓋多個領域。 一個包含程式設計、架構、安全、設計與個人專案筆記的 vault,能從跨領域檢索中受益——這是以專案為範圍的
CLAUDE.md無法提供的。 - 涉及隱私的內容。 本機優先代表檢索管線絕不會將內容傳送至外部服務。vault 中僅包含您放入的內容,包括您不會上傳至雲端服務的資料。
心智模型:三層架構
此系統由三個獨立運作但結合時可相互強化的層級構成。每一層各自關注不同的問題,也各有不同的失效模式。
┌─────────────────────────────────────────────────────┐
│ INTEGRATION LAYER │
│ MCP servers, hooks, skills, context injection │
│ Concern: delivering context to AI tools │
│ Failure: wrong context, too much context, stale │
└──────────────────────┬──────────────────────────────┘
│ query + ranked results
┌──────────────────────┴──────────────────────────────┐
│ RETRIEVAL LAYER │
│ BM25, vector KNN, RRF fusion, token budget │
│ Concern: finding the right content for any query │
│ Failure: wrong ranking, missed results, slow queries │
└──────────────────────┬──────────────────────────────┘
│ chunked, embedded, indexed
┌──────────────────────┴──────────────────────────────┐
│ INTAKE LAYER │
│ Note creation, signal triage, vault organization │
│ Concern: what enters the vault and how it's stored │
│ Failure: noise, duplicates, missing structure │
└─────────────────────────────────────────────────────┘
Intake 決定哪些內容進入 vault。若缺乏策展,vault 會累積雜訊:推文截圖、未附註解的複製貼上文章、沒有脈絡的半成品思緒。Intake 層負責在入口處把關品質。評分管線、標籤規範或人工審核流程——任何能確保 vault 僅容納值得檢索內容的機制皆可。
Retrieval 讓 vault 具備可查詢性。這是核心引擎:將筆記 chunking 為搜尋單位、將 chunks 嵌入向量空間、建立關鍵字與語意搜尋索引,並以 RRF 融合結果。檢索層將一個檔案目錄轉化為可查詢的 knowledge base。若缺少此層,vault 僅能透過手動瀏覽與基本搜尋存取,而無法以程式化方式供 AI 工具使用。
Integration 將檢索層與 AI 工具相連。MCP 伺服器將檢索功能暴露為可呼叫的工具。Hooks 自動注入情境。Skills 則將新知識回寫至 vault。整合層是 knowledge base 與消費它的 AI agent 之間的介面。
各層的設計刻意解耦。Intake 評分管線對 embeddings 一無所知。檢索器對訊號路由規則一無所知。MCP 伺服器對筆記的建立方式一無所知。這種解耦意味著您可以獨立改進任何一層。更換 embedding 模型而不需變動 intake 管線。新增 MCP 功能而不需修改檢索器。調整訊號評分的啟發式規則而不需觸及索引。
為 AI 消費而設計的 Vault 架構
為 AI 檢索所優化的 vault 與為個人瀏覽所優化的 vault 遵循不同的慣例。本節涵蓋資料夾結構、筆記 schema、frontmatter 慣例,以及能提升檢索品質的特定模式。
資料夾結構
為頂層資料夾使用數字前綴,建立可預測的組織階層。這些數字並非代表優先順序——而是用來將相關領域歸類,讓結構一目了然。
vault/
├── 00-inbox/ # Unsorted captures, pending triage
├── 01-projects/ # Active project notes
├── 02-areas/ # Ongoing areas of responsibility
├── 03-resources/ # Reference material by topic
│ ├── programming/
│ ├── security/
│ ├── ai-engineering/
│ ├── design/
│ └── devops/
├── 04-archive/ # Completed projects, old references
├── 05-signals/ # Scored signal intake
│ ├── ai-tooling/
│ ├── security/
│ ├── systems/
│ └── ...12 domain folders
├── 06-daily/ # Daily notes (if used)
├── 07-templates/ # Note templates (excluded from index)
├── 08-attachments/ # Images, PDFs (excluded from index)
├── .obsidian/ # Obsidian config (excluded from index)
└── .indexignore # Paths to exclude from retrieval index
應納入索引的資料夾:所有包含 markdown 散文內容的資料夾——projects、areas、resources、signals、daily notes。
應排除於索引之外的資料夾:templates(內含佔位變數,而非實際內容)、attachments(二進位檔案)、Obsidian 設定,以及任何包含您不希望納入檢索索引的敏感內容的資料夾。
.indexignore 檔案
在 vault 根目錄建立 .indexignore 檔案,以明確排除不納入檢索索引的路徑。語法與 .gitignore 相同:
# Obsidian internal
.obsidian/
# Templates contain placeholders, not content
07-templates/
# Binary attachments
08-attachments/
# Personal health/medical notes
02-areas/health/
# Financial records
02-areas/finance/personal/
# Career documents (resumes, salary data)
02-areas/career/private/
索引器會在掃描前讀取此檔案,並完全跳過符合的路徑。位於排除路徑中的檔案不會被 chunked、不會產生 embeddings,也永遠不會出現在搜尋結果中。
筆記 Schema
每一則筆記都應具備 YAML frontmatter。檢索器會使用 frontmatter 欄位進行篩選與情境強化:
---
title: "OAuth Token Rotation Patterns"
type: note # note | signal | project | moc | daily
domain: security # primary domain for routing
tags:
- authentication
- oauth
- token-management
created: 2026-01-15
updated: 2026-02-28
source: "" # URL if captured from external source
status: active # active | archived | draft
---
檢索所需的必要欄位:
title— 用於搜尋結果顯示,以及 BM25 的標題情境type— 可支援類型篩選查詢(「只顯示 MOCs」或「只顯示 signals」)tags— 以 0.3 權重建入 FTS5 標題情境索引,即使本文使用不同的詞彙,也能提供關鍵字匹配
非必要但具價值的欄位:
domain— 可支援領域範圍查詢(「只搜尋 security 筆記」)source— 為擷取內容提供出處歸屬;檢索器可在結果中納入來源 URLstatus— 允許從有效搜尋中排除已封存或草稿筆記
Chunking 慣例
檢索器會以 H2(##)標題為界進行 chunking。這意味著您的筆記結構會直接影響檢索的顆粒度:
有利於檢索:
## Token Rotation Strategy
The rotation interval depends on the threat model...
## Implementation with refresh_token
The OAuth 2.0 refresh token flow requires...
## Error Handling: Expired Tokens
When a token expires mid-request...
三個 H2 區段會產生三個可獨立搜尋的 chunks。每個 chunk 都具備足夠的情境,讓 embedding 能捕捉其語意。針對「expired token handling」的查詢,會精準匹配到第三個 chunk。
不利於檢索:
# OAuth Notes
Token rotation depends on threat model. The OAuth 2.0 refresh
token flow requires storing the refresh token securely. When a
token expires mid-request, the client should retry after refresh.
The rotation interval is typically 15-30 minutes for access tokens
and 7-30 days for refresh tokens...
一個沒有 H2 標題的長區段只會產生一個龐大的 chunk。embedding 會在該區段的所有主題之間取平均值。針對任何子主題的查詢,都會等同地匹配整則筆記。
經驗法則:若某區段涵蓋多個概念,請將其拆分為 H2 子區段。剩下的交給 chunker 處理即可。
哪些內容不該放進筆記
會降低檢索品質的內容:
- 未經註解、整篇複製貼上的文章。檢索器會為原文章的關鍵字建立索引,使您的 vault 充斥非您親筆撰寫的內容。請改為新增摘要、擷取重點,或連結至原始來源 URL。
- 沒有文字描述的螢幕截圖。檢索器索引的是 markdown 文字。沒有 alt 文字或周圍描述的圖片,對 BM25 與向量搜尋而言都是不可見的。
- 憑證字串。API 金鑰、tokens、密碼、連線字串。即使有憑證過濾機制,最安全的做法仍是絕不將機密貼入筆記中。請改以名稱引用(例如「
~/.env中的 Cloudflare API token」)。 - 未經整理的自動生成內容。若某工具會自動生成筆記(會議逐字稿、Readwise highlights、RSS 匯入),請先審閱並加以註解,再讓它進入永久 vault。未經整理的自動匯入只會增加資料量,卻無法帶來可檢索的價值。
AI 工作流程的外掛生態系
能提升 vault 品質以利 AI 檢索的 Obsidian 外掛可分為三類:結構類(強化一致性)、查詢類(暴露 metadata)與同步類(維持 vault 的即時性)。
必備外掛
Dataview。以 frontmatter 欄位將您的 vault 當作資料庫來查詢。可建立動態索引,例如「所有標註 security 且近 30 天內更新的筆記」或「所有狀態為 active 的專案筆記」。Dataview 本身不會直接協助檢索,但能幫助您找出 vault 中的覆蓋缺口,並定位需要更新的筆記。
TABLE type, domain, updated
FROM "03-resources"
WHERE status = "active"
SORT updated DESC
LIMIT 20
Templater。以具備動態欄位的範本建立筆記。透過預先填入 created、type 與 domain 欄位的範本,確保每一則新筆記都以正確的 frontmatter 開始。一致的 frontmatter 能提升檢索篩選的品質。
<%* /* New Resource Note Template */ %>
---
title: "<% tp.file.cursor() %>"
type: note
domain: <% tp.system.suggester(["programming", "security", "ai-engineering", "design", "devops"], ["programming", "security", "ai-engineering", "design", "devops"]) %>
tags: []
created: <% tp.date.now("YYYY-MM-DD") %>
updated: <% tp.date.now("YYYY-MM-DD") %>
source: ""
status: active
---
## Key Points
## Details
## 參考資源
Linter。 在整個 vault 中強制執行格式規則。一致的標題階層(H1 為標題、H2 為章節、H3 為子章節)可確保 chunker 產生可預測的結果。對檢索有意義的 Linter 規則:
- 標題遞增:強制連續的標題層級(不得從 H1 跳至 H3)
- YAML 標題:需與檔名相符
- 尾端空格:移除(避免 FTS5 斷詞產生雜訊)
- 連續空行:限制為 1 行(產生更乾淨的 chunks)
Git 整合。 為您的 vault 提供版本控制。追蹤變更歷程、在多台機器間同步,並可從誤刪中復原。Git 同時提供 mtime 資料,讓索引器用於增量變更偵測。
有助於索引的外掛
Smart Connections。 一款在 Obsidian 內提供 AI 驅動語意搜尋的 Obsidian 外掛。Smart Connections v4 預設會建立本地 embeddings——vault 建立索引後,語意連結與查找即可完全離線運作,無須任何 API 呼叫。23 v4.3.0(2026年4月1日) 新增了連結清單的圖形檢視(可切換,並可設定為預設顯示)、連結面板的可設定停駐位置,以及索引中斷後更可靠的 block embedding 復原機制。該版本也推出了「Substrate」,這是一個跨外掛環境,讓 Smart Connections、Smart Chat 與 Smart Composer 能共享狀態。33 雖然本指南中的檢索系統是 Obsidian 的外部系統(以 Python pipeline 形式執行),但 Smart Connections 在寫作過程中探索語意關聯仍相當實用。兩套系統索引相同的內容,但服務不同的使用情境:Smart Connections 用於編輯器內的探索,外部檢索器則透過 MCP 整合至 AI 工具。
2026年4月推出的 AI 原生外掛。 一波新的社群外掛直接鎖定 Claude Code / Codex / Gemini-CLI 工作流程:
| 外掛 | 發布日期 | 功能 |
|---|---|---|
| Cortex | 4月4日 | 由 Claude Code 驅動的 vault 代理——將 vault 視為代理工作空間,而不僅是筆記儲存庫 |
| VaultSearch | 4月7日 | 本地優先的 hybrid 搜尋:BM25 + 語意 + 模糊比對(與本指南的檢索堆疊直接重疊) |
| LLM Wiki | 4月9日 | 將您的 vault 轉化為可私密查詢的知識庫 |
| Drift | 4月11日 | VS Code 風格的 diff 檢視器,用於 AI 驅動的 Obsidian 編輯;專為 Claude Code 工作流程設計 |
| EngramQuest | 4月11日 | 從筆記生成記憶挑戰;隨附適用於 Claude Code / Gemini CLI / Cursor 的「AI Skills」 |
| Hybrid Search MCP | 3月(仍屬新發布) | MCP 伺服器 + CLI 結合 BM25 與語意搜尋——專為 AI 助理而打造 |
請將此視為正在浮現的新興領域——其中幾款未來幾季內很可能會整合或被納入 Smart Connections / Obsidian 核心。若您今天要挑選一款,VaultSearch 與 Hybrid Search MCP 在理念上最接近本指南的外部檢索器。
Dataview 補充: Dataview(長期存在的 Obsidian 查詢外掛)最後一次發布 0.5.70 是在 2025年4月,此後實質上處於停滯狀態。對於新的工作,Obsidian 內建的 Bases 功能(1.9+)是隱性的繼任者,也是建議採用的路徑。
Metadata Menu。 提供結構化的 frontmatter 編輯,並為欄位值提供自動完成。可減少 type、domain 與 tags 欄位的錯字。一致的 metadata 能提升檢索過濾的準確度。
有害於索引的外掛
Excalidraw。 將繪圖以 JSON 形式嵌入於 markdown 檔案中。該 JSON 雖然在語法上是合法的 markdown,但在 chunking 與 embedding 時會產出無意義的內容。請透過 .indexignore 或依副檔名過濾,將 Excalidraw 檔案排除於索引之外。
Kanban。 將看板狀態儲存為特殊格式的 markdown。該格式是為 Kanban 渲染而設計,並非為散文檢索而設。chunker 會產出卡片標題與 metadata 的片段,這些內容無法良好地嵌入。請將 Kanban 看板排除於索引之外。
Calendar。 建立內容極少的每日筆記(通常僅有日期標題)。空白或近乎空白的筆記會產生品質低落的 chunks。若您使用每日筆記,請在其中撰寫具體內容,或將每日筆記資料夾排除於索引之外。
值得注意的外掛設定
File recovery → 啟用。 防止意外刪除筆記。雖然與檢索無直接關聯,但對於您所仰賴的知識庫至關重要。
Strict line breaks → 停用。 符合 markdown 標準的換行方式(以雙換行符分隔段落)產生的 chunks 比 Obsidian 的嚴格模式(以單換行符對應 <br>)更為乾淨。
Default new file location → 指定資料夾。 將新檔案導向 00-inbox/,避免未分類的筆記污染各領域資料夾。inbox 是暫存區;檔案經過初步分類後再移至對應的領域資料夾。
Wiki-link format → 盡可能使用最短路徑。 較短的連結目標在索引連結結構時,檢索器更容易解析。
嵌入模型:選擇與設定
嵌入模型將文字片段轉換為數值向量以進行語意搜尋。模型的選擇決定了檢索品質、索引大小、嵌入速度以及執行時相依性。本節將說明為何 Model2Vec 的 potion-base-8M 是預設選擇,以及何時應選擇其他替代方案。
為什麼選擇 Model2Vec potion-base-8M
模型: minishlab/potion-base-8M
參數量: 760 萬
維度: 256
大小: ~30 MB
相依性: model2vec(僅需 numpy,無需 PyTorch)
推論: 僅使用 CPU、靜態詞嵌入(無注意力層)
Model2Vec 將 sentence transformer 的知識蒸餾為靜態 token 嵌入。它不像 BERT、MiniLM 及其他 transformer 模型那樣在輸入上執行注意力層,而是透過對預先計算的 token 嵌入進行加權平均來產生向量。5 實際效果是:嵌入速度比 transformer 類模型快 50 至 500 倍,因為沒有序列式運算。
在 MTEB 基準測試套件上,potion-base-8M 達到 all-MiniLM-L6-v2 效能的 89%(平均分數 50.03 vs 56.09)。6 這 11% 的品質差距,是換取速度與簡潔性的代價。對於較短的 markdown 片段(典型 vault 中平均為 200 至 400 字),品質差異比在較長文件上來得不明顯,因為兩種模型對於簡短、聚焦的文字會收斂至相似的表示。
設定
# embedder.py
DEFAULT_MODEL = "minishlab/potion-base-8M"
EMBEDDING_DIM = 256
class Model2VecEmbedder:
def __init__(self, model_name=DEFAULT_MODEL):
self._model_name = model_name
self._model = None
def _ensure_model(self):
if self._model is not None:
return
_activate_venv() # Add isolated venv to sys.path
from model2vec import StaticModel
self._model = StaticModel.from_pretrained(self._model_name)
def embed_batch(self, texts):
self._ensure_model()
vecs = self._model.encode(texts)
return [v.tolist() for v in vecs]
延遲載入。 模型在首次使用時才會載入,並非在匯入時就載入。當檢索器以 BM25-only 備援模式運作時(例如尚未安裝嵌入專用的 venv),匯入 embedder 模組不會產生任何成本。
隔離的虛擬環境。 模型在專屬的 venv 中執行(例如 ~/.claude/venvs/memory/),以避免與工具鏈其餘部分的相依性衝突。_activate_venv() 函式會在執行時將該 venv 的 site-packages 加入 sys.path。
# Create isolated venv
python3 -m venv ~/.claude/venvs/memory
~/.claude/venvs/memory/bin/pip install model2vec
批次處理。 embedder 以 64 筆為一批處理文字,以攤平 Model2Vec 的額外開銷。indexer 會將片段以批次方式送入 embed_batch(),而不是逐一嵌入。
何時應選擇替代方案
| 模型 | 維度 | 大小 | 速度 | 品質(MTEB) | 最適合 |
|---|---|---|---|---|---|
| potion-base-8M | 256 | 30 MB | 500x | 50.03 | 預設:本地、快速、無 GPU |
| potion-base-32M | 256 | 120 MB | 400x | 52.46 | 品質更高,仍屬靜態嵌入 |
| potion-retrieval-32M | 256 | 120 MB | 400x | 36.35(檢索) | 針對檢索最佳化的靜態模型 |
| potion-multilingual-128M | 256 | ~500 MB | 300x | — | 多語系 vault(101 種語言) |
| all-MiniLM-L6-v2 | 384 | 80 MB | 1x | 56.09 | 品質更高,仍可本地執行 |
| nomic-embed-text-v1.5 | 768 | 270 MB | 0.5x | 62.28 | 最佳本地品質 |
| text-embedding-3-small | 1536 | API | N/A | 62.30 | 基於 API、最高品質 |
選擇 potion-base-32M,當您希望比 potion-base-8M 擁有更好的品質,又不想脫離靜態嵌入家族時。此模型於 2025 年 1 月發布,使用從 baai/bge-base-en-v1.5 蒸餾而來的更大詞彙表,達到 52.46 的 MTEB 平均分數(比 potion-base-8M 提升 5%),同時維持相同的 256 維輸出與僅需 numpy 的相依性。20 模型檔案大 4 倍會增加記憶體用量,但嵌入速度仍比 transformer 模型快上數個數量級。
選擇 potion-retrieval-32M,當您的主要使用情境是檢索(vault 搜尋正是如此)時。此變體是從 potion-base-32M 專門針對檢索任務微調而來,在 MTEB 檢索基準上取得 36.35 分,相較於基礎模型的 33.52 分更高。20 整體 MTEB 平均分數下降至 49.73,是因為微調以通用效能換取了檢索專精的提升。
選擇 potion-multilingual-128M,當您的 vault 中包含多種語言的筆記時。此模型於 2025 年 5 月發布,支援 101 種語言,是多語系任務中表現最佳的靜態嵌入模型。它能為任何語言的文字產生嵌入,同時維持與其他 potion 模型相同、僅需 numpy 的相依性。24 較大的模型檔案(~500 MB)是換取跨語言能力的代價。若您的筆記除了英文外,還包含日文、中文、德文或其他非英文內容,不妨選用此模型。
選擇 all-MiniLM-L6-v2,當檢索品質比速度更重要,且您已安裝 PyTorch 時。384 維的向量相較於 256 維會使 SQLite 資料庫大小增加約 50%。在 M 系列硬體上對 15,000 個檔案進行完整重新索引時,嵌入速度會從少於 1 分鐘降至約 10 分鐘。
選擇 nomic-embed-text-v1.5,當您需要最佳的本地檢索品質,並可接受較慢的索引速度時。768 維的向量會使資料庫大小大約增為三倍。需要 PyTorch 以及較新型的 CPU 或 GPU。
選擇 text-embedding-3-small,當網路延遲與隱私是可接受的取捨時。API 能產生品質最高的嵌入,但會引入雲端相依性、依 token 計費的成本(每百萬 token 0.02 美元),並將您的內容傳送至 OpenAI 的伺服器。
其他所有情況則維持使用 potion-base-8M。 速度優勢對迭代式索引(開發期間的重新索引)至關重要,僅依賴 numpy 可避免 PyTorch 安裝的複雜性,而 256 維的向量也能保持資料庫精簡。
量化與維度縮減
Model2Vec v0.5.0 以後支援以較低精度與較少維度載入模型。20 這對於在資源受限的硬體上部署,或在不更換模型的前提下縮減資料庫大小相當實用:
from model2vec import StaticModel
# Load with int8 quantization (25% of original size)
model = StaticModel.from_pretrained("minishlab/potion-base-8M", quantize=True)
# Load with reduced dimensions (e.g., 128 instead of 256)
model = StaticModel.from_pretrained("minishlab/potion-base-8M", dimensionality=128)
量化後的模型能以極小的記憶體佔用維持近乎相同的檢索品質。維度縮減採用 Matryoshka 風格的截斷方式——前 N 個維度承載了最主要的資訊。將維度從 256 縮減到 128 可讓向量儲存空間減半,而對短文字檢索的品質損失微乎其微。
截至 2025 年 5 月,Model2Vec 也已支援 BPE 與 Unigram tokenizer(除了 WordPiece 之外),這擴大了可蒸餾為靜態模型的 sentence transformer 範圍。22
為 Vault 特製嵌入進行微調
Model2Vec v0.4.0 以後支援在靜態嵌入之上訓練自訂分類模型,v0.7.0 則新增了詞彙量化與可設定的蒸餾 pooling 方式。22 這對於擁有特殊詞彙的 vault(醫學筆記、法律參考資料、特定領域行話)相當有意義,因為預設的 potion 模型可能無法捕捉其語意細節:
from model2vec import StaticModel
from model2vec.train import train_model
# Fine-tune on vault-specific data
model = StaticModel.from_pretrained("minishlab/potion-base-8M")
trained_model = train_model(model, train_texts, train_labels)
trained_model.save_pretrained("./vault-embeddings")
對多數 vault 而言,預設的 potion-base-8M 已能提供足夠的檢索品質。只有在檢索持續遺漏通用模型無法捕捉的領域特定關聯時,才值得進行微調。
模型雜湊追蹤
indexer 會儲存一個由模型名稱與詞彙表大小衍生而來的雜湊值。若您更換嵌入模型,indexer 會在下一次增量執行時偵測到不一致,並自動觸發完整重新索引。
def _compute_model_hash(self):
"""Hash model name + vocab size for compatibility tracking."""
key = f"{self._model_name}:{self._model.vocab_size}"
return hashlib.sha256(key.encode()).hexdigest()[:16]
這可避免將不同模型的向量混存於同一資料庫中,否則會產生毫無意義的 cosine similarity 分數。
失敗情境
模型下載失敗。 首次執行時會從 Hugging Face 下載模型。若下載失敗(網路問題、企業防火牆),檢索器會退回至 BM25-only 模式。模型在首次下載後便會快取於本地。
維度不一致。 若您在未清空資料庫的情況下切換模型,儲存的向量會與新嵌入擁有不同維度。indexer 會透過模型雜湊偵測此情況,並觸發完整重新索引。若雜湊檢查失效(自訂模型缺乏適當的雜湊值),sqlite-vec 會在執行 KNN 查詢時因維度不符而發生錯誤。
大型 vault 的記憶體壓力。 在單一批次中嵌入 50,000 個以上的片段,可能會耗用大量記憶體。indexer 以 64 筆為一批處理,以限制尖峰記憶體用量。若記憶體仍吃緊,可再調降批次大小。
使用FTS5進行全文檢索
SQLite的FTS5擴充功能提供了具備BM25排序的全文檢索能力。FTS5是hybrid檢索管線中的關鍵字搜尋元件。本節涵蓋FTS5的設定、BM25擅長的情境,以及其特定的失效模式。
FTS5虛擬資料表
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text,
section,
heading_context,
content=chunks,
content_rowid=id
);
內容同步模式。content=chunks參數告訴FTS5直接參照chunks資料表,而非儲存一份文字的重複副本。這讓儲存需求減半,但代價是當chunks被插入、更新或刪除時,FTS5必須手動同步。
欄位。共建立三個欄位索引:
- chunk_text — 每個chunk的主要內容(BM25權重:1.0)
- section — H2標題文字(BM25權重:0.5)
- heading_context — 筆記標題、標籤與metadata(BM25權重:0.3)
BM25排序
BM25依據詞頻、逆向文件頻率以及文件長度正規化來為文件排序。FTS5中的bm25()輔助函式接受各欄位個別的權重:
SELECT
c.id, c.file_path, c.section, c.chunk_text,
bm25(chunks_fts, 1.0, 0.5, 0.3) AS score
FROM chunks_fts
JOIN chunks c ON chunks_fts.rowid = c.id
WHERE chunks_fts MATCH ?
ORDER BY score
LIMIT 30;
欄位權重(1.0、0.5、0.3)的意義為:
- chunk_text中的關鍵字命中對分數貢獻最大
- section(標題)中的命中貢獻減半
- heading_context(標題、標籤)中的命中貢獻為30%
這些權重皆可調整。若您的vault擁有強烈預示內容品質的描述性標題,可提高section權重;若標籤完整且準確,則可提高heading_context權重。
BM25的強項
BM25擅長處理包含精確識別字的查詢:
- 函式名稱:
_rrf_fuse、embed_batch、get_stale_files - CLI旗標:
--incremental、--vault、--model - 設定鍵:
bm25_weight、max_tokens、batch_size - 錯誤訊息:
SQLITE_LOCKED、ConnectionRefusedError - 特定專業術語:
PostToolUse、PreToolUse、AGENTS.md
對於這類查詢,BM25能立即找到精確匹配。向量搜尋雖然會回傳語意相關的內容,但可能將精確匹配的結果排在概念性討論之後。
BM25的弱項
當查詢使用的術語與儲存內容不同時,BM25便會失效:
- 查詢:「how to handle authentication failures」→ vault中的筆記使用「login error recovery」與「session expiration handling」。BM25因關鍵字不同而無法匹配。
- 查詢:「what is the best way to manage state」→ vault中的筆記討論「Redux store patterns」與「context providers」。BM25因為「state management」是透過特定技術名稱來表達而漏掉這些結果。
BM25在規模化下還會面臨關鍵字碰撞問題。在一個15,000個檔案的vault中,搜尋「configuration」會命中數百則筆記,因為幾乎每篇專案筆記都提到configuration。搜尋結果雖技術上正確,實務上卻毫無用處——排序機制無法判斷哪一則「configuration」筆記與當前查詢相關。
FTS5分詞器
FTS5預設使用unicode61分詞器,可處理ASCII與Unicode文字。若vault包含大量CJK(中文、日文、韓文)內容,建議考慮使用trigram分詞器:
-- For CJK-heavy vaults
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text, section, heading_context,
content=chunks, content_rowid=id,
tokenize='trigram'
);
預設的unicode61分詞器依詞彙邊界進行切分,對於字詞之間沒有空格的語言效果不佳。trigram分詞器則是每三個字元切分一次,讓子字串比對得以實現,代價是索引體積會增加(約為原本的3倍)。
維護
當底層的chunks資料表變更時,FTS5需要明確的同步作業:
# After inserting chunks
cursor.execute("""
INSERT INTO chunks_fts(chunks_fts)
VALUES('rebuild')
""")
rebuild指令會從內容資料表重新建構FTS5索引。請在批次插入(完整重建索引)後執行,但不要在個別的增量更新後執行——對於那些情況,請改用INSERT INTO chunks_fts(rowid, chunk_text, section, heading_context)來同步個別的資料列。
使用 sqlite-vec 進行向量搜尋
sqlite-vec 擴充套件將向量 KNN(K-Nearest Neighbors,K 個最近鄰)搜尋功能帶入 SQLite。本節涵蓋 sqlite-vec 的設定、從筆記到可搜尋向量的 embedding 管線,以及特定的查詢模式。
sqlite-vec 虛擬資料表
CREATE VIRTUAL TABLE chunk_vecs USING vec0(
id INTEGER PRIMARY KEY,
embedding float[256]
);
vec0 模組以打包的二進位資料形式儲存 256 維的浮點向量。id 欄位與 chunks 資料表 1:1 對應,讓向量結果得以與 chunk 的中繼資料進行 join。
Embedding 管線
管線從筆記流向可搜尋的向量:
Note (.md file)
→ Chunker: split at H2 boundaries
→ Chunks (30-2000 chars each)
→ Credential filter: scrub secrets
→ Embedder: Model2Vec encode
→ Vectors (256-dim float arrays)
→ sqlite-vec: store as packed binary
→ Ready for KNN queries
向量序列化
Python 的 struct 模組會將浮點向量序列化以供 sqlite-vec 儲存:
import struct
def _serialize_vector(vec):
"""Pack float list into binary for sqlite-vec."""
return struct.pack(f"{len(vec)}f", *vec)
def _deserialize_vector(blob, dim=256):
"""Unpack binary blob to float list."""
return list(struct.unpack(f"{dim}f", blob))
KNN 查詢
向量搜尋查詢會先對輸入查詢進行 embedding,再依餘弦距離找出 K 個最近的 chunk:
def _vector_search(self, query_text, limit=30):
query_vec = self.embedder.embed_batch([query_text])[0]
packed = _serialize_vector(query_vec)
results = self.db.execute("""
SELECT
cv.id,
cv.distance,
c.file_path,
c.section,
c.chunk_text
FROM chunk_vecs cv
JOIN chunks c ON cv.id = c.id
WHERE embedding MATCH ?
AND k = ?
ORDER BY distance
""", [packed, limit]).fetchall()
return results
sqlite-vec 中的 MATCH 運算子會執行近似最近鄰搜尋。k 參數控制回傳的結果數量。distance 欄位則包含餘弦距離(0 代表完全相同,2 代表完全相反)。
使用距離限制進行 KNN 分頁
自 sqlite-vec v0.1.7 起,KNN 查詢支援 WHERE distance < ? 限制條件,可透過游標進行分頁,瀏覽大型結果集時無須重新掃描先前的頁面。26
def _paginated_vector_search(self, query_vec, page_size=20, max_distance=None):
"""Paginate through KNN results using distance constraints."""
packed = _serialize_vector(query_vec)
constraint = f"AND distance < {max_distance}" if max_distance else ""
results = self.db.execute(f"""
SELECT cv.id, cv.distance, c.file_path, c.chunk_text
FROM chunk_vecs cv
JOIN chunks c ON cv.id = c.id
WHERE embedding MATCH ?
AND k = ?
{constraint}
ORDER BY distance
""", [packed, page_size]).fetchall()
# Use last result's distance as cursor for next page
next_cursor = results[-1][1] if results else None
return results, next_cursor
這取代了以往取得較大 k 值後再於 Python 中切片的模式,可降低大型 vault 進行探索性查詢時的記憶體使用量。
vec0 資料表的 DELETE 支援
sqlite-vec v0.1.7 為 vec0 虛擬資料表新增了原生 DELETE 支援。26 先前若要移除向量,必須捨棄整張資料表後重新建立。現在索引器的檔案移除流程可以直接刪除向量:
# Before v0.1.7: required workaround (drop + recreate, or mark as inactive)
# After v0.1.7: direct DELETE works
db.execute("DELETE FROM chunk_vecs WHERE id = ?", [chunk_id])
這簡化了筆記被刪除或搬移時的增量重新索引作業。索引器不再需要維護影子「作用中 ID」資料表,也不需要批次重建。
向量搜尋的強項
向量搜尋在「概念比具體字詞更重要」的查詢上表現出色:
- 查詢:「how to handle authentication failures」→ 找出關於「login error recovery」的筆記(相同的語意空間,但關鍵字不同)
- 查詢:「what patterns exist for caching」→ 找出關於「memoization」、「Redis TTL strategies」以及「HTTP cache headers」的筆記(相關概念,但用語多元)
- 查詢:「approaches to testing asynchronous code」→ 找出關於「pytest-asyncio fixtures」、「mock event loops」以及「async test patterns」的筆記(相同概念但以實作細節表達)
向量搜尋的弱項
向量搜尋在精確識別符方面表現不佳:
- 查詢:
_rrf_fuse→ 回傳關於「fusion algorithms」與「rank merging」的筆記,但實際函式定義的排名可能低於概念性討論 - 查詢:
PostToolUse→ 回傳關於「tool lifecycle hooks」與「post-execution handlers」的筆記,而非特定 hook 名稱本身
向量搜尋也難以處理結構化資料。JSON 設定檔、YAML 區塊以及程式碼片段所產生的 embedding 會捕捉結構模式,而非語意。含有 "review": true 的 JSON 檔案,其 embedding 會與散文形式討論程式碼審查的 embedding 截然不同。
優雅降級
若 sqlite-vec 載入失敗(例如缺少擴充套件、平台不相容或函式庫損毀),檢索器會退回至僅使用 BM25 搜尋的模式:
class VectorIndex:
def __init__(self, db_path):
self.db = sqlite3.connect(db_path)
self._vec_available = False
try:
self.db.enable_load_extension(True)
self.db.load_extension("vec0")
self._vec_available = True
except Exception:
pass # BM25-only mode
@property
def vec_available(self):
return self._vec_available
檢索器會先檢查 vec_available 才嘗試向量查詢。停用時,所有搜尋僅使用 BM25,且會略過 RRF 融合步驟。
Reciprocal Rank Fusion (RRF)
RRF可合併兩份已排序的清單,無須校準分數。本節涵蓋演算法、查詢追蹤實例、k參數的調校方式,以及選擇RRF而非其他方案的原因。若想使用可編輯排名的互動式計算器、情境預設與視覺化架構瀏覽器,請參閱hybrid retriever深入剖析。
演算法
RRF僅依據文件在各清單中的排名位置為其評分:
score(d) = Σ (weight_i / (k + rank_i))
其中:
- k為平滑常數(60,依照Cormack等人的建議3)
- rank_i為文件在結果清單i中的排名(從1開始計算)
- weight_i為可選的每份清單乘數(預設為1.0)
在多份清單中排名皆佳的文件,融合分數較高。僅出現於單一清單的文件,只會獲得該來源提供的分數。
為何選擇RRF而非其他方案
加權線性組合需要將BM25分數與餘弦距離進行校準。BM25分數無上限,且會隨語料庫規模變化;餘弦距離則界於[0, 2]之間。兩者合併時必須正規化,而正規化參數又會因資料集而異。RRF只使用排名位置,無論採用何種評分方式,排名皆為從1開始的整數。
學習式融合模型需要帶標註的訓練資料,也就是查詢與文件的相關性配對。對個人知識庫而言,這類訓練資料根本不存在。您得親自判斷數百組查詢與文件配對,才能訓練出有用的模型。RRF則完全不需要訓練資料。
Condorcet投票法(Borda count、Schulze method)在理論上雖然優雅,實作與調校卻較為複雜。原始RRF論文已證實,RRF在TREC評估資料上的表現優於Condorcet方法。3
融合實例
查詢:”how does the review aggregator handle disagreements”
BM25將review-aggregator.py排在第3位(關鍵字”review”、”aggregator”、”disagreements”完全吻合),但將兩份設定檔排得更前面(因為”review”在其中的出現更為顯著)。向量搜尋則把同一段chunk排在第1位(在衝突解決的語意上相符)。經過RRF融合後:
| Chunk | BM25 | Vec | 融合分數 |
|---|---|---|---|
| review-aggregator.py “Disagreement Resolution” | #3 | #1 | 0.0323 |
| code-review-patterns.md “Multi-Reviewer” | #4 | #2 | 0.0317 |
| deliberation-config.json “Review Weights” | #1 | — | 0.0164 |
在兩份清單中排名皆佳的chunk會浮到最上方。只出現在單一清單的chunk只能獲得單一來源的分數,因而落在雙清單結果之下。實際處理衝突解決邏輯的chunk之所以勝出,是因為兩種方法都找到了它——BM25透過關鍵字,向量搜尋透過語意。
若想查看逐步追蹤與各排名的RRF計算細節,不妨在互動式RRF計算器中嘗試不同的k值。
實作
RRF_K = 60
def _rrf_fuse(self, bm25_results, vec_results,
bm25_weight=1.0, vec_weight=1.0):
"""Fuse BM25 and vector results using Reciprocal Rank Fusion."""
scores = {}
for rank, r in enumerate(bm25_results, start=1):
cid = r["id"]
if cid not in scores:
scores[cid] = {
"rrf_score": 0.0,
"file_path": r["file_path"],
"section": r["section"],
"chunk_text": r["chunk_text"],
"bm25_rank": None,
"vec_rank": None,
}
scores[cid]["rrf_score"] += bm25_weight / (self._rrf_k + rank)
scores[cid]["bm25_rank"] = rank
for rank, r in enumerate(vec_results, start=1):
cid = r["id"]
if cid not in scores:
scores[cid] = {
"rrf_score": 0.0,
"file_path": r["file_path"],
"section": r["section"],
"chunk_text": r["chunk_text"],
"bm25_rank": None,
"vec_rank": None,
}
scores[cid]["rrf_score"] += vec_weight / (self._rrf_k + rank)
scores[cid]["vec_rank"] = rank
fused = sorted(
scores.values(),
key=lambda x: x["rrf_score"],
reverse=True,
)
return fused
調校k
k常數控制高排名結果與低排名結果之間的權重分配:
- 較低的k(例如10): 高排名結果居於主導地位。第1名得分為1/11 = 0.091,第10名為1/20 = 0.050(差距1.8倍)。適用於您信任個別排序器能正確找出最佳結果的情境。
- 預設k(60): 表現均衡。第1名得分為1/61 = 0.0164,第10名為1/70 = 0.0143(差距1.15倍)。排名差距受到壓縮,使得「出現在多份清單中」這一點獲得更高的權重。
- 較高的k(例如200): 同時出現在兩份清單的重要性,遠大於排名位置本身。第1名得分為1/201,第10名為1/210——幾乎沒有差別。適用於個別排序器輸出雜訊較多,但跨清單一致性可靠的情境。
建議從k=60開始。 原始RRF論文發現此值在各種TREC資料集上均表現穩健。唯有在自身查詢分布上量測到失敗案例後,才需要進一步調整。
同分處理
當兩個chunk的RRF分數完全相同時(較為罕見,但在某一清單中排名相同且另一清單皆未出現時可能發生),可依下列順序打破平手:
- 優先選擇同時出現在兩份清單中的chunk,而非僅出現於單一清單者
- 在同時出現於兩份清單的chunk之中,優先選擇合計排名較低者
- 在僅出現於單一清單的chunk之中,優先選擇該清單中排名較低者
完整檢索流程
本節追蹤一個查詢從輸入到輸出經過整個流程的過程:BM25 搜尋、向量搜尋、RRF 融合、token 預算截斷以及上下文組裝。
端對端流程
User query: "PostToolUse hook for context compression"
│
├─ BM25 Search (FTS5)
│ → MATCH "PostToolUse hook context compression"
│ → Top 30 results ranked by BM25 score
│ → 12ms
│
├─ Vector Search (sqlite-vec)
│ → Embed query with Model2Vec
│ → KNN k=30 on chunk_vecs
│ → Top 30 results ranked by cosine distance
│ → 8ms
│
└─ RRF Fusion
→ Merge 60 candidates (may overlap)
→ Score by rank position
→ Top 10 results
→ 3ms
│
└─ Token Budget
→ Truncate to max_tokens (default 4000)
→ Estimate at 4 chars per token
→ Return results with metadata
→ <1ms
總延遲:約 23ms,測試環境為 49,746 個 chunks 的資料庫,執行於 Apple M3 Pro 硬體上。
Search API
class HybridRetriever:
def search(self, query, limit=10, max_tokens=4000,
bm25_weight=1.0, vec_weight=1.0):
"""
Search the vault using hybrid BM25 + vector retrieval.
Args:
query: Search query text
limit: Maximum results to return
max_tokens: Token budget for total result text
bm25_weight: Weight for BM25 results in RRF
vec_weight: Weight for vector results in RRF
Returns:
List of SearchResult with file_path, section,
chunk_text, rrf_score, bm25_rank, vec_rank
"""
# BM25 search
bm25_results = self._bm25_search(query, limit=30)
# Vector search (if available)
if self.index.vec_available:
vec_results = self._vector_search(query, limit=30)
fused = self._rrf_fuse(
bm25_results, vec_results,
bm25_weight, vec_weight,
)
else:
fused = bm25_results # BM25-only fallback
# Token budget truncation
results = []
token_count = 0
for r in fused[:limit]:
chunk_tokens = len(r["chunk_text"]) // 4
if token_count + chunk_tokens > max_tokens:
break
results.append(r)
token_count += chunk_tokens
return results
Token 預算截斷
max_tokens 參數可避免檢索器回傳超過 AI 工具能處理範圍的上下文。估算方式採用每 4 個字元對應 1 個 token(對英文散文而言是合理的近似值)。結果採貪婪式截斷:依排序順序加入結果,直到預算用盡為止。
這是較為保守的策略。更精緻的做法會考量每筆結果的品質分數,優先選擇較短、品質較高的結果,而非較長、品質較低的結果。貪婪做法較為單純,且在實務上運作良好,因為 RRF 排序已經依相關性對結果進行排序。
資料庫結構(完整版)
-- Chunk content and metadata
CREATE TABLE chunks (
id INTEGER PRIMARY KEY,
file_path TEXT NOT NULL,
section TEXT NOT NULL,
chunk_text TEXT NOT NULL,
heading_context TEXT DEFAULT '',
mtime_ns INTEGER NOT NULL,
embedded_at REAL NOT NULL
);
CREATE INDEX idx_chunks_file ON chunks(file_path);
CREATE INDEX idx_chunks_mtime ON chunks(mtime_ns);
-- FTS5 for BM25 search (content-synced to chunks table)
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text, section, heading_context,
content=chunks, content_rowid=id
);
-- sqlite-vec for vector KNN search
CREATE VIRTUAL TABLE chunk_vecs USING vec0(
id INTEGER PRIMARY KEY,
embedding float[256]
);
-- Model metadata for compatibility tracking
CREATE TABLE model_meta (
key TEXT PRIMARY KEY,
value TEXT
);
優雅降級路徑
Full pipeline: BM25 + Vector + RRF → Best results
No sqlite-vec: BM25 only → Good results (no semantic)
No model download: BM25 only → Good results (no semantic)
No FTS5: Vector only → Decent results (no keyword)
No database: Error → Prompt user to run indexer
檢索器會在初始化時檢查各項能力,並據以調整查詢策略。缺少某個元件會降低品質,但不會造成錯誤。唯一的硬性失敗情境是找不到資料庫檔案。
生產環境數據
測量環境:vault 包含 16,894 個檔案、49,746 個 chunks、83 MB 的 SQLite 資料庫,執行於 Apple M3 Pro:
| 指標 | 數值 |
|---|---|
| 總檔案數 | 16,894 |
| 總 chunk 數 | 49,746 |
| 資料庫大小 | 83 MB |
| BM25 查詢延遲 (p50) | 12ms |
| 向量查詢延遲 (p50) | 8ms |
| RRF 融合延遲 | 3ms |
| 端對端搜尋延遲 (p50) | 23ms |
| 完整重建索引時間 | 約 4 分鐘 |
| 增量重建索引時間 | <10 秒 |
| 嵌入模型 | potion-base-8M (256-dim) |
| BM25 候選池 | 30 |
| 向量候選池 | 30 |
| 預設結果數上限 | 10 |
| 預設 token 預算 | 4,000 tokens |
內容雜湊與變更偵測
索引器需要知道自上次索引執行後,哪些檔案發生了變更。本節說明變更偵測機制以及雜湊策略。
檔案修改時間比對
索引器會在 chunks 資料表中為每個 chunk 儲存 mtime_ns(以奈秒為單位的檔案修改時間)。進行增量執行時,索引器會:
- 掃描 vault 中所有位於允許資料夾內的
.md檔案 - 從檔案系統讀取每個檔案的
mtime_ns - 與資料庫中儲存的
mtime_ns進行比對 - 區分為三種類別:
- 新增檔案: 檔案系統中存在但資料庫中不存在的路徑
- 變更檔案: 兩邊都存在,但
mtime_ns不同的路徑 - 刪除檔案: 資料庫中存在但檔案系統中不存在的路徑
def get_stale_files(self, vault_mtimes):
"""Find files whose mtime changed or are new."""
stored = dict(self.db.execute(
"SELECT DISTINCT file_path, mtime_ns FROM chunks"
).fetchall())
stale = []
for path, mtime in vault_mtimes.items():
if path not in stored or stored[path] != mtime:
stale.append(path)
return stale
def get_deleted_files(self, vault_paths):
"""Find files in database that no longer exist in vault."""
stored_paths = set(r[0] for r in self.db.execute(
"SELECT DISTINCT file_path FROM chunks"
).fetchall())
return stored_paths - set(vault_paths)
為何採用 mtime 而非內容雜湊
內容雜湊(檔案內容的 SHA-256)會比 mtime 比對更可靠——它能偵測到檔案被觸碰但內容未變更的情況(例如 git checkout 還原了原始的 mtime)。然而,雜湊需要在每次增量執行時讀取每個檔案。以 16,894 個檔案為例,讀取檔案內容需耗時 2 至 3 秒,而從檔案系統讀取 mtime 則不到 100ms。
取捨在此:mtime 比對偶爾會對未變更的檔案觸發不必要的重新索引(誤判),但絕不會漏掉實際的變更。誤判的代價只是每次執行多耗費幾次嵌入呼叫。對於每次 AI 互動都要執行的系統而言,100ms 與 3 秒之間的速度差異,使 mtime 成為務實的選擇。
處理刪除作業
當檔案從 vault 中被刪除時,索引器會從資料庫中移除該檔案的所有 chunks:
def remove_file(self, file_path):
"""Remove all chunks and vectors for a file."""
chunk_ids = [r[0] for r in self.db.execute(
"SELECT id FROM chunks WHERE file_path = ?",
[file_path],
).fetchall()]
for cid in chunk_ids:
self.db.execute(
"DELETE FROM chunk_vecs WHERE id = ?", [cid]
)
self.db.execute(
"DELETE FROM chunks WHERE file_path = ?",
[file_path],
)
DELETE FROM chunk_vecs 語句自 sqlite-vec v0.1.7 起可原生運作。26 較舊版本需要變通做法(丟棄並重建虛擬表,或維護一組外部的「啟用 ID」集合)。若使用早於 0.1.7 的版本,請先升級再依賴直接刪除的做法。
FTS5 的 content-sync 資料表需透過 INSERT INTO chunks_fts(chunks_fts, rowid, ...) VALUES('delete', ?, ...) 為每個被移除的列進行明確刪除。索引器會在檔案移除流程中一併處理此操作。
增量索引與全量索引
索引器支援兩種模式:增量(快速,日常使用)與全量(緩慢,偶爾使用)。本節說明何時使用哪種模式、冪等性保證,以及損毀復原。
增量索引
何時使用: 編輯筆記後的日常索引。預設模式。
運作方式: 1. 掃描 vault 以偵測檔案變更(比較 mtime) 2. 刪除已刪除檔案的 chunks 3. 重新 chunk 並重新嵌入已變更的檔案 4. 為新檔案插入新 chunks 5. 同步 FTS5 索引
典型耗時: 在 16,000 個檔案的 vault 上,處理一天的編輯所需時間 <10 秒。
python index_vault.py --incremental
全量索引
何時使用: - 變更嵌入模型後(偵測到模型雜湊不符) - schema 遷移後(新增欄位、變更索引) - 資料庫損毀後(完整性檢查失敗) - 增量索引產生非預期結果時
運作方式: 1. 刪除所有現有資料(chunks、向量、FTS5 條目) 2. 掃描整個 vault 3. Chunk 所有檔案 4. 嵌入所有 chunks 5. 從頭建立 FTS5 索引
典型耗時: 在 Apple M3 Pro 上處理 16,894 個檔案約需 4 分鐘。
python index_vault.py --full
冪等性
兩種模式皆具冪等性:執行相同指令兩次會產生相同結果。索引器會在插入新 chunks 之前先刪除檔案的現有 chunks,因此對已是最新狀態的資料庫重新執行增量索引,不會產生任何變更。重新執行全量索引則會產生完全相同的資料庫。
損毀復原
若 SQLite 資料庫發生損毀(寫入期間斷電、磁碟錯誤、交易中途程序遭終止):
# Check integrity
sqlite3 vectors.db "PRAGMA integrity_check;"
# If corruption detected, full reindex rebuilds from source files
python index_vault.py --full
真實來源永遠是 vault 檔案,而非資料庫。資料庫只是可隨時重建的衍生產物。這是關鍵的設計特性:您永遠不需要備份資料庫。
--incremental 旗標
當索引器以 --incremental 執行時:
- 模型雜湊檢查。 比較儲存的模型雜湊與當前模型。若不同,自動切換至全量索引模式並警告使用者。
- 檔案掃描。 遍歷允許的資料夾,收集檔案路徑與 mtime。
- 變更偵測。 與儲存的資料比較。
- 批次處理。 以 64 個為一批重新 chunk 並重新嵌入已變更的檔案。
- 進度回報。 顯示已處理檔案數量與經過時間。
- 優雅關閉。 處理 SIGINT 時,先完成當前檔案再停止。
憑證過濾與資料邊界
個人筆記中藏有秘密:API 金鑰、bearer tokens、資料庫連線字串、除錯過程中貼上的私密金鑰。憑證過濾器可防止這些內容進入檢索索引。
問題所在
一則關於除錯 OAuth 整合的筆記可能包含:
The token was: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
I used this curl command:
curl -H "Authorization: Bearer sk-ant-api03-abc123..."
若沒有過濾,JWT 和 API 金鑰都會被 chunk、嵌入並儲存到資料庫中。搜尋「authentication」就會傳回包含真實秘密的 chunk。更糟的是,若檢索器透過 MCP 將結果餵給 AI 工具,這些秘密便會出現在 AI 的 context window 中,甚至可能出現在該工具的日誌裡。
基於模式的過濾
憑證過濾器會在每個 chunk 儲存前執行,比對 25 種廠商特定模式加上通用模式:
廠商特定模式:
| 模式 | 範例 | Regex |
|---|---|---|
| OpenAI API 金鑰 | sk-... |
sk-[a-zA-Z0-9_-]{20,} |
| Anthropic API 金鑰 | sk-ant-api03-... |
sk-ant-api\d{2}-[a-zA-Z0-9_-]{20,} |
| GitHub PAT | ghp_... |
gh[ps]_[a-zA-Z0-9]{36,} |
| AWS Access Key | AKIA... |
AKIA[0-9A-Z]{16} |
| Stripe 金鑰 | sk_live_... |
[sr]k_(live\|test)_[a-zA-Z0-9]{24,} |
| Cloudflare token | ... |
多種模式 |
通用模式:
| 模式 | 偵測方式 |
|---|---|
| JWT tokens | eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+ |
| Bearer tokens | Bearer\s+[a-zA-Z0-9_\-\.]+ |
| 私密金鑰 | -----BEGIN (RSA\|EC\|OPENSSH) PRIVATE KEY----- |
| 高熵 base64 | 熵值 >4.5 bits/char 且長度 40+ 字元的字串 |
| 密碼指派 | password\s*[:=]\s*["'][^"']+["'] |
過濾器實作
def clean_content(text):
"""Scrub credentials from text before indexing."""
result = ScanResult(is_clean=True, match_count=0, patterns=[])
for pattern in CREDENTIAL_PATTERNS:
matches = pattern.regex.findall(text)
if matches:
text = pattern.regex.sub(
f"[REDACTED:{pattern.name}]", text
)
result.is_clean = False
result.match_count += len(matches)
result.patterns.append(pattern.name)
return text, result
關鍵設計選擇:
-
在嵌入之前過濾。 被嵌入的是已清理的文字。向量表示永遠不會編碼憑證模式。搜尋「API key」會傳回討論 API 金鑰管理的筆記,而非包含實際金鑰的筆記。
-
取代而非移除。
[REDACTED:pattern-name]標記保留了周圍文字的語意脈絡。嵌入能捕捉到「此處曾有類似憑證的內容」,而不會編碼憑證本身。 -
記錄模式,而非值。 過濾器會記錄哪些模式被比對中(例如「Scrubbed 2 credential(s) from oauth-debug.md [jwt, bearer-token]」),但絕不記錄憑證的實際值。
基於路徑的排除
.indexignore 檔案以路徑提供粗粒度排除。憑證過濾器則在已索引的檔案內進行細粒度清理。兩者缺一不可:
- 對於明知包含敏感內容的整個資料夾(健康筆記、財務紀錄、職涯文件),使用
.indexignore - 對於意外嵌入在原本可索引內容中的秘密,使用憑證過濾器
資料分級
對於內容多元的 vault,建議依敏感度為筆記分級:
| 等級 | 範例 | 索引? | 過濾? |
|---|---|---|---|
| 公開 | 部落格草稿、技術筆記 | 是 | 是 |
| 內部 | 專案計畫、架構決策 | 是 | 是 |
| 敏感 | 薪資資料、健康紀錄 | 否(.indexignore) | N/A |
| 機密 | 憑證、私密金鑰 | 否(.indexignore) | N/A |
MCP Server 架構
Model Context Protocol(MCP)伺服器將檢索器作為工具公開,讓 AI 代理能夠呼叫。本節涵蓋伺服器設計、能力範圍與權限邊界。
協定選擇:STDIO 或 HTTP
MCP 支援兩種傳輸模式:
STDIO — AI 工具將 MCP 伺服器作為子程序啟動,並透過 stdin/stdout 進行通訊。這是本機工具的標準模式。Claude Code、Codex CLI 與 Cursor 皆支援 STDIO MCP 伺服器。
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/path/to/vault",
"DB_PATH": "/path/to/vectors.db"
}
}
}
}
HTTP — MCP 伺服器作為獨立的 HTTP 服務執行。適用於遠端存取、多用戶端設定,或 vault 位於共用伺服器的團隊配置。
{
"mcpServers": {
"obsidian": {
"url": "http://localhost:3333/mcp"
}
}
}
建議: 個人 vault 請使用 STDIO。它更簡單、更安全(無網路曝險),且伺服器生命週期由 AI 工具管理。只有當多個工具或多台機器需要並行存取同一個 vault 時,才使用 HTTP。
MCP 規格演進。 2025 年 6 月的 MCP 規格新增了 OAuth 2.1 授權、結構化工具輸出(具型別的返回 schema)以及 elicitation(由伺服器發起的使用者提示)。2025 年 11 月的版本則將 Streamable HTTP 納入為一級傳輸模式,並引入
.well-knownURL 探索以自動瀏覽伺服器能力、宣告工具為唯讀或可變動的結構化工具註解,以及 SDK 層級標準化系統。1821 下一個規格版本(暫定 2026 年中)提出了長時間執行任務的非同步操作、針對醫療與金融等產業的領域特定協定擴充,以及多代理工作流程的代理對代理通訊標準。21 對於個人 vault 伺服器而言,STDIO 仍是最簡單的路徑。Streamable HTTP 傳輸與.well-known探索主要有利於具備多租戶路由與負載平衡的企業 HTTP 部署。請關注 MCP 路線圖,以掌握影響傳輸選擇的更新。
能力設計
MCP 伺服器應公開最小化的工具集:
search — 主要工具。執行 hybrid 檢索並返回排序後的結果。
{
"name": "obsidian_search",
"description": "Search the Obsidian vault using hybrid BM25 + vector retrieval",
"parameters": {
"query": { "type": "string", "description": "Search query" },
"limit": { "type": "integer", "default": 5 },
"max_tokens": { "type": "integer", "default": 2000 }
}
}
read_note — 依路徑讀取特定筆記的完整內容。當代理希望查看搜尋結果的完整脈絡時相當實用。
{
"name": "obsidian_read_note",
"description": "Read the full content of a note by file path",
"parameters": {
"file_path": { "type": "string", "description": "Relative path within vault" }
}
}
list_notes — 列出符合篩選條件(依資料夾、標籤、類型或日期範圍)的筆記。當代理沒有特定查詢時,適合用於探索。
{
"name": "obsidian_list_notes",
"description": "List notes matching filters",
"parameters": {
"folder": { "type": "string", "description": "Folder path within vault" },
"tag": { "type": "string", "description": "Tag to filter by" },
"limit": { "type": "integer", "default": 20 }
}
}
get_context — 便利工具,執行搜尋並將結果格式化為適合注入對話的 context 區塊。
{
"name": "obsidian_get_context",
"description": "Get formatted context from vault for a topic",
"parameters": {
"topic": { "type": "string", "description": "Topic to get context for" },
"max_tokens": { "type": "integer", "default": 2000 }
}
}
權限邊界
MCP 伺服器應強制執行嚴格的邊界:
-
唯讀。 伺服器讀取 vault 與索引資料庫。不會建立、修改或刪除筆記。寫入操作(擷取新筆記)由獨立的 hook 或 skill 處理,而非 MCP 伺服器。
-
限定於 vault 範圍。 伺服器只讀取配置的 vault 路徑內的檔案。路徑遍歷嘗試(
../../etc/passwd)必須被拒絕。 -
憑證過濾輸出。 即使資料庫包含預先過濾過的內容,仍應在輸出時套用憑證過濾作為縱深防禦措施。
-
Token 受限回應。 對所有工具回應強制執行
max_tokens,以防止 AI 工具收到過於龐大的 context 區塊。
錯誤處理
MCP 工具應返回結構化的錯誤訊息,協助 AI 工具復原:
def search(self, query, limit=5, max_tokens=2000):
if not self.db_path.exists():
return {
"error": "Index database not found. Run the indexer first.",
"suggestion": "python index_vault.py --full"
}
results = self.retriever.search(query, limit, max_tokens)
if not results:
return {
"results": [],
"message": f"No results found for '{query}'. Try broader terms."
}
return {
"results": [
{
"file_path": r["file_path"],
"section": r["section"],
"text": r["chunk_text"],
"score": round(r["rrf_score"], 4),
}
for r in results
],
"count": len(results),
"query": query,
}
Claude Code Integration
Claude Code是Obsidian檢索系統的主要使用者。本節涵蓋MCP設定、hook整合以及obsidian_bridge.py模式。
MCP 設定
將Obsidian MCP伺服器加入~/.claude/settings.json:
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/absolute/path/to/vault",
"DB_PATH": "/absolute/path/to/vectors.db"
}
}
}
}
加入設定後,請重新啟動Claude Code。MCP伺服器會以子程序形式啟動。驗證其運作狀態:
> What tools do you have from the obsidian MCP server?
Claude Code應該會列出可用的工具(obsidian_search、obsidian_read_note等)。
Hook整合
Hooks會在指定的生命週期節點擴充Claude Code的行為。有兩個hook與Obsidian整合相關:
PreToolUse hook — 在代理處理工具呼叫前查詢vault,自動注入相關的上下文內容。
#!/bin/bash
# ~/.claude/hooks/pre-tool-use/obsidian-context.sh
# Automatically inject vault context before tool execution
TOOL_NAME="$1"
PROMPT="$2"
# Only inject context for code-related tools
case "$TOOL_NAME" in
Edit|Write|Bash)
# Query the vault
CONTEXT=$(python /path/to/retriever.py search "$PROMPT" --limit 3 --max-tokens 1500)
if [ -n "$CONTEXT" ]; then
echo "---"
echo "Relevant vault context:"
echo "$CONTEXT"
echo "---"
fi
;;
esac
PostToolUse hook — 將重要的工具輸出擷取回vault,供未來檢索使用。
#!/bin/bash
# ~/.claude/hooks/post-tool-use/capture-insight.sh
# Capture significant outputs to vault (selective)
TOOL_NAME="$1"
OUTPUT="$2"
# Only capture substantial outputs
if [ ${#OUTPUT} -gt 500 ]; then
python /path/to/capture.py --text "$OUTPUT" --source "claude-code-$TOOL_NAME"
fi
obsidian_bridge.py 模式
橋接模組提供一個Python API,供hooks和skills呼叫:
# obsidian_bridge.py
from retriever import HybridRetriever
_retriever = None
def get_retriever():
global _retriever
if _retriever is None:
_retriever = HybridRetriever(
db_path="/path/to/vectors.db",
vault_path="/path/to/vault",
)
return _retriever
def search_vault(query, limit=5, max_tokens=2000):
"""Search vault and return formatted context."""
retriever = get_retriever()
results = retriever.search(query, limit, max_tokens)
if not results:
return ""
lines = ["## Vault Context\n"]
for r in results:
lines.append(f"**{r['file_path']}** — {r['section']}")
lines.append(f"> {r['chunk_text'][:500]}")
lines.append("")
return "\n".join(lines)
/capture 技能
一個用來將洞察擷取回vault的Claude Code技能:
/capture "OAuth token rotation requires both access and refresh token invalidation"
--domain security
--tags oauth,tokens
此技能會在00-inbox/建立一個新筆記,附帶適當的frontmatter,並觸發增量重新索引,讓新筆記立即可供搜尋。
自訂指令模式
Claude Code技能可以將vault操作封裝為具名指令。實務工作者已建立起Obsidian專屬的指令庫,將vault同時視為讀取來源和寫入目標。
訊號掃描。 /scan-intel指令會查詢外部來源,依據個人研究興趣對發現進行評分,並將符合條件的訊號以帶frontmatter的方式寫入vault筆記:
/scan-intel --topics "agent infrastructure, security" --lookback 7d
此指令會從設定好的來源(arXiv、HN、RSS)擷取內容,套用評分模型(相關性、可行性、深度、權威性),並將通過的訊號寫入主題專屬的vault資料夾。vault因此成為自動化情報管線的下游消費者。
Captain’s log。 /captains-log指令會彙整所有儲存庫每日的git活動,將結構化的日誌條目寫入vault,並納入所做的決策、領悟以及待處理的議題:
/captains-log
此指令會從GitHub拉取commit歷史,依儲存庫分組,並格式化為敘事式的日誌條目。日積月累,這些每日日誌會形成一份可搜尋的紀錄,呈現已交付的成果及其背後的原因。
Obsidian擷取。 /obsidian-capture指令會將目前Claude Code工作階段中的洞察,連同適當的metadata直接寫入vault:
/obsidian-capture "SAST gates in agent loops increase security degradation"
--folder AI-Tools --tags security,agents
此模式可延伸至任何vault操作:建立MOCs、更新專案狀態筆記、連結相關訊號,或從累積的每日日誌產生每週摘要。
社群範例。 實務工作者正陸續發布他們的指令庫。一位開發者分享了22個自訂的Obsidian + Claude Code指令,涵蓋每日回顧、專案規劃、研究擷取以及內容工作流程。1另一位則打造了一個「Visual Explainer」技能,可從程式碼分析產生vault中的圖表筆記。2指令雖有差異,架構卻一致:Claude Code技能作為介面、vault筆記作為儲存層,檢索基礎設施則作為查詢引擎。
上下文視窗管理
整合時應留意Claude Code的上下文視窗:
- 每次查詢注入的上下文應限制在1,500-2,000個token。 超過此範圍會與代理的工作記憶互相排擠。
- 納入來源歸屬。 務必附上檔案路徑和章節標題,讓代理可以參照來源。
- 截斷chunk文字。 較長的chunk應以
...截斷,而非整段省略。前300-500個字元通常已包含關鍵資訊。 - 不要在每次工具呼叫時都注入。 PreToolUse hook應依據所呼叫的工具選擇性地注入上下文。讀取操作不需要vault上下文;寫入和編輯操作才會因此受益。
Codex CLI Integration
Codex CLI透過config.toml連接到MCP伺服器。其整合模式在設定語法和指令傳遞上與Claude Code不同。
MCP 設定
加入.codex/config.toml或~/.codex/config.toml:
[mcp_servers.obsidian]
command = "python"
args = ["/path/to/obsidian_mcp.py"]
[mcp_servers.obsidian.env]
VAULT_PATH = "/absolute/path/to/vault"
DB_PATH = "/absolute/path/to/vectors.db"
AGENTS.md 模式
Codex CLI會讀取AGENTS.md取得專案層級的指示。請納入vault搜尋指引:
## Available Tools
### Obsidian Vault (MCP: obsidian)
Use the `obsidian_search` tool to find relevant context from the knowledge base.
Search the vault when you need:
- Background on a concept or pattern
- Prior decisions or rationale
- Reference material for implementation
Example queries:
- "authentication patterns in FastAPI"
- "how does the review aggregator work"
- "sqlite-vec configuration"
與 Claude Code 的差異
| 功能 | Claude Code | Codex CLI |
|---|---|---|
| MCP設定 | settings.json |
config.toml |
| Hooks | ~/.claude/hooks/ |
不支援 |
| Skills | ~/.claude/skills/ |
不支援 |
| 指示檔案 | CLAUDE.md |
AGENTS.md |
| 核准模式 | --dangerously-skip-permissions |
suggest / auto-edit / full-auto |
關鍵差異: Codex CLI不支援hooks。因此無法使用自動注入上下文的模式(PreToolUse hook)。取而代之的作法是在AGENTS.md中納入明確的指示,告訴代理在開始工作前先搜尋vault。
Cursor 與其他工具
Cursor 以及其他支援 MCP 的 AI 工具,都可以連接到同一個 Obsidian MCP server。本節說明常見工具的設定方式。
Cursor
在專案根目錄的 .cursor/mcp.json 中加入以下設定:
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/absolute/path/to/vault",
"DB_PATH": "/absolute/path/to/vectors.db"
}
}
}
}
Cursor 的 .cursorrules 檔案可以加入使用 vault 的指示:
When working on implementation tasks, search the Obsidian vault
for relevant context before writing code. Use the obsidian_search
tool with descriptive queries about the concept you're implementing.
相容性對照表
| 工具 | MCP 支援 | 傳輸協定 | 設定位置 |
|---|---|---|---|
| Claude Code | 完整 | STDIO | ~/.claude/settings.json |
| Codex CLI | 完整 | STDIO | .codex/config.toml |
| Cursor | 完整 | STDIO | .cursor/mcp.json |
| Windsurf | 完整 | STDIO | .windsurf/mcp.json |
| Continue.dev | 部分 | HTTP | ~/.continue/config.json |
| Zed | 開發中 | STDIO | 設定介面 |
| Claudian(Obsidian 外掛) | 不適用(內嵌) | Claude Code CLI | Obsidian 外掛設定 |
| Agent Client(Obsidian 外掛) | 不適用(內嵌) | ACP | Obsidian 外掛設定 |
針對不支援 MCP 工具的備援方案
對於不支援 MCP 的工具,可以將 retriever 包裝成 CLI:
# Search from command line
python retriever_cli.py search "query text" --limit 5
# Output formatted for copy-paste into any tool
python retriever_cli.py context "query text" --format markdown
CLI 會輸出結構化文字,可以手動貼到任何 AI 工具的輸入欄位。雖然不如 MCP 整合那麼優雅,但適用於所有工具。
從結構化筆記進行 Prompt Caching
Vault 中的結構化筆記可以作為可重複使用的 context 區塊,降低 AI 互動間的 token 用量。本節說明快取鍵值設計與 token 預算管理。
這個模式
與其在每次互動時都去搜尋 context,不如從結構良好的 vault 筆記預先建置 context 區塊並加以快取:
# cache_keys.py
CONTEXT_BLOCKS = {
"auth-patterns": {
"vault_query": "authentication patterns implementation",
"max_tokens": 1500,
"ttl_hours": 24, # Rebuild daily
},
"api-conventions": {
"vault_query": "API design conventions REST patterns",
"max_tokens": 1000,
"ttl_hours": 168, # Rebuild weekly
},
"project-architecture": {
"vault_query": "current project architecture decisions",
"max_tokens": 2000,
"ttl_hours": 12, # Rebuild twice daily
},
}
快取失效機制
快取失效依據兩個訊號:
- TTL 到期。每個 context 區塊都有存活時間。當 TTL 到期時,系統會重新查詢 vault 以重建該區塊。
- Vault 變更偵測。當索引器偵測到曾貢獻給某個已快取 context 區塊的檔案發生變更時,該區塊會立即失效。
Token 預算管理
一個工作階段會有一個總 context 預算。已快取的區塊會佔用其中一部分預算:
Total context budget: 8,000 tokens
├─ System prompt: 1,500 tokens
├─ Cached blocks: 3,000 tokens (pre-loaded)
├─ Dynamic search: 2,000 tokens (on-demand)
└─ Conversation: 1,500 tokens (remaining)
已快取的區塊會在工作階段開始時載入。動態搜尋結果則依每次查詢填入剩餘預算。這種混合式作法讓 agent 擁有常用 context 的基礎層,同時保留預算給特定查詢使用。
快取前後的 Token 用量比較
未啟用快取時:每一次相關查詢都會觸發 vault 搜尋,回傳 1,500 至 2,000 tokens 的 context。在一個工作階段中若進行 10 次查詢,agent 會消耗 15,000 至 20,000 tokens 的 vault context。
啟用快取後:三個預先建置的 context 區塊共消耗 4,500 tokens。額外的搜尋則為每個獨特查詢再增加 1,500 至 2,000 tokens。在 10 次查詢中若有 6 次由快取區塊涵蓋,agent 會消耗 4,500 + (4 * 1,500) = 10,500 tokens,約為未快取情況的一半。
用 PostToolUse Hook 進行 Context 壓縮
工具輸出往往冗長:堆疊追蹤、檔案清單、測試結果皆是如此。PostToolUse hook 可以在這些輸出佔用 context window 空間之前先行壓縮。
問題所在
執行測試的 Bash 工具呼叫可能會回傳:
PASSED tests/test_auth.py::test_login_success
PASSED tests/test_auth.py::test_login_failure
PASSED tests/test_auth.py::test_token_refresh
PASSED tests/test_auth.py::test_session_expiry
... (200 more lines)
FAILED tests/test_api.py::test_rate_limit_exceeded
完整輸出有 5,000 tokens,但真正的訊號只存在於兩行內:200 個通過、1 個失敗。
Hook 實作
#!/bin/bash
# ~/.claude/hooks/post-tool-use/compress-output.sh
# Compress verbose tool outputs to preserve context window
TOOL_NAME="$1"
OUTPUT="$2"
OUTPUT_LEN=${#OUTPUT}
# Only compress large outputs
if [ "$OUTPUT_LEN" -lt 2000 ]; then
exit 0 # Pass through unchanged
fi
case "$TOOL_NAME" in
Bash)
# Compress test output
if echo "$OUTPUT" | grep -q "PASSED\|FAILED"; then
PASSED=$(echo "$OUTPUT" | grep -c "PASSED")
FAILED=$(echo "$OUTPUT" | grep -c "FAILED")
FAILURES=$(echo "$OUTPUT" | grep "FAILED")
echo "Tests: $PASSED passed, $FAILED failed"
if [ "$FAILED" -gt 0 ]; then
echo "Failures:"
echo "$FAILURES"
fi
fi
;;
esac
防止遞迴觸發
若未加防護,會輸出內容的壓縮 hook 可能觸發自己:
# Guard against recursive invocation
if [ -n "$COMPRESS_HOOK_ACTIVE" ]; then
exit 0
fi
export COMPRESS_HOOK_ACTIVE=1
壓縮啟發式規則
| 輸出類型 | 偵測方式 | 壓縮策略 |
|---|---|---|
| 測試結果 | PASSED / FAILED 關鍵字 |
計算通過/失敗數,只顯示失敗項目 |
| 檔案清單 | 指令中出現 ls 或 find |
截斷為前 20 筆加上總數 |
| 堆疊追蹤 | Traceback 關鍵字 |
保留首尾兩個 frame 加上錯誤訊息 |
| Git 狀態 | modified: / new file: |
依狀態彙整總數 |
| 建置輸出 | warning: / error: |
去除資訊行,保留警告與錯誤 |
訊號收集與分流管線
收集層決定哪些內容會進入 vault。若缺乏策展,vault 便會累積雜訊。本節說明將訊號路由至各領域資料夾的評分管線。
來源
訊號來自多個管道:
- RSS feeds:技術部落格、資安公告、版本發布說明
- 書籤:透過 Obsidian Web Clipper 或 bookmarklet 儲存的瀏覽器書籤
- 電子報:電子報中的重點摘錄
- 手動擷取:閱讀、對話或研究過程中寫下的筆記
- 工具輸出:透過 hooks 擷取具代表性的 AI 工具輸出
- iOS Share Extension:Obsidian 的 iOS 應用程式(於 2026 年初更新)內建 Share Extension,可將 Safari、社群網路及其他應用程式的內容直接儲存至 vault,無需開啟 Obsidian。31 這提供了一條低摩擦的行動裝置收集路徑——從 Safari 分享一篇文章,它就會以待評分的 vault 筆記形式抵達。
- Obsidian CLI:Shell 指令稿與 hooks 可以透過
obsidian file create建立筆記,或透過obsidian file append附加至現有筆記,讓桌面端實現自動化收集管線。
評分維度
每個訊號會在四個維度上進行評分(各為 0.0 至 1.0):
| 維度 | 問題 | 低分(0.0-0.3) | 高分(0.7-1.0) |
|---|---|---|---|
| 相關性 | 這是否與我當下關注的領域有關? | 僅有枝微末節的關聯、超出範圍 | 與當前工作直接相關 |
| 可行動性 | 我是否能運用這份資訊? | 純理論、無實際應用 | 可直接套用的特定技巧或模式 |
| 深度 | 內容的實質性如何? | 僅有標題、淺層摘要 | 附帶範例的深入分析 |
| 權威性 | 來源的可信度如何? | 匿名部落格、未經驗證 | 原始來源、同儕審閱、公認專家 |
綜合分數與路由
composite = (relevance * 0.35) + (actionability * 0.25) +
(depth * 0.25) + (authority * 0.15)
| 分數區間 | 動作 |
|---|---|
| 0.55+ | 自動路由至領域資料夾 |
| 0.40 - 0.55 | 進入人工審閱佇列 |
| < 0.40 | 捨棄(不予儲存) |
領域路由
分數超過 0.55 的訊號,會依關鍵字比對與主題分類,路由至 12 個領域資料夾之一:
05-signals/
├── ai-tooling/ # Claude, LLMs, AI development tools
├── security/ # Vulnerabilities, auth, cryptography
├── systems/ # Architecture, distributed systems
├── programming/ # Languages, patterns, algorithms
├── web/ # Frontend, backends, APIs
├── data/ # Databases, data engineering
├── devops/ # CI/CD, containers, infrastructure
├── design/ # UI/UX, product design
├── mobile/ # iOS, Android, cross-platform
├── career/ # Industry trends, hiring, growth
├── research/ # Academic papers, whitepapers
└── other/ # Signals that don't fit a domain
實際運行統計
經過 14 個月的運作:
| 指標 | 數值 |
|---|---|
| 處理的訊號總數 | 7,771 |
| 自動路由(>0.55) | 4,832(62%) |
| 排入人工審閱(0.40-0.55) | 1,543(20%) |
| 捨棄(<0.40) | 1,396(18%) |
| 使用中的領域資料夾 | 12 |
| 每日平均訊號數 | 約 18 |
知識圖譜模式
Obsidian的wiki-link圖譜編碼了筆記之間的關係。本節涵蓋連結語意、用於脈絡擴展的圖譜遍歷,以及會降低圖譜品質的反模式。
反向連結語意
每個wiki-link都會在圖譜中建立一條有向邊。Obsidian同時追蹤正向連結與反向連結:
- 正向連結: 筆記A包含
[[Note B]]→ A連結至B - 反向連結: 筆記B顯示筆記A引用了它
圖譜根據脈絡編碼不同類型的關係:
| 連結模式 | 語意 | 範例 |
|---|---|---|
| 行內連結 | 「與…相關」 | 「詳見[[OAuth Token Rotation]]」 |
| 標題連結 | 「具有子主題」 | 「## Related\n- [[Token Rotation]]\n- [[Session Management]]」 |
| 類標籤連結 | 「被歸類為」 | 「[[type/reference]]」 |
| MOC連結 | 「屬於…的一部分」 | 列出相關筆記的Map of Content筆記 |
Maps of Content(MOCs)
MOCs是將相關筆記組織成可導覽結構的索引筆記:
---
title: "Authentication & Security MOC"
type: moc
domain: security
---
## Core Concepts
- [[OAuth 2.0 Overview]]
- [[JWT Token Anatomy]]
- [[Session Management Patterns]]
## Implementation Patterns
- [[OAuth Token Rotation]]
- [[Refresh Token Security]]
- [[PKCE Flow Implementation]]
## Failure Modes
- [[Token Expiry Handling]]
- [[Session Fixation Prevention]]
- [[CSRF Defense Strategies]]
MOCs透過兩種方式提升檢索效果:
- 直接命中。 搜尋「authentication overview」會匹配到MOC本身,為代理提供一份經過策劃的相關筆記清單。
- 脈絡擴展。 在找到特定筆記之後,檢索器可以檢查該筆記是否出現在任何MOC中,並將MOC的結構納入結果,讓代理掌握更廣泛主題的地圖。
用於脈絡擴展的圖譜遍歷
檢索器的未來強化方向:在找到前幾名結果後,透過跟隨連結擴展脈絡:
def expand_context(results, depth=1):
"""Follow wiki-links from top results to find related context."""
expanded = set()
for result in results:
# Parse wiki-links from chunk text
links = extract_wiki_links(result["chunk_text"])
for link_target in links:
# Resolve link to file path
target_path = resolve_wiki_link(link_target)
if target_path and target_path not in expanded:
expanded.add(target_path)
# Include target's most relevant chunk
target_chunks = get_chunks_for_file(target_path)
# ... rank and include best chunk
return results + list(expanded_results)
此功能尚未在目前的檢索器中實作,但代表圖譜結構的自然延伸。
反模式
孤立叢集。 一群筆記彼此互相連結,卻與vault的其餘部分毫無關聯。Obsidian的圖譜面板會將這些顯示為斷開的孤島。孤立叢集意味著缺少MOCs或缺少跨領域連結。
標籤氾濫。 不一致地使用標籤,或建立過多細粒度標籤。一個擁有5,000筆記卻有500個獨特標籤的vault,平均每10個標籤才對應1筆筆記——這些標籤對篩選毫無用處。請整併為20至50個對應領域資料夾的高層級標籤。
連結過多、內容貧乏的筆記。 完全由wiki-link組成、毫無敘述性內容的筆記。這類筆記的索引效果很差,因為chunker沒有可嵌入的文字。至少加上一段說明為何這些連結筆記彼此相關的脈絡文字。
所有事物都用雙向連結。 並非每個引用都需要成為wiki-link。順帶提及「OAuth」並不需要寫成[[OAuth 2.0 Overview]]。請將wiki-link保留給有意識、可導覽的關係——也就是點擊連結能提供有用脈絡的情況。
開發者工作流程範例
以下是將vault檢索與日常開發任務結合的實用工作流程。
晨間脈絡載入
從載入相關脈絡展開一天的工作:
Search my vault for notes about [current project] updated in the last week
檢索器會傳回關於您目前專案的近期筆記,讓您快速回顧昨日進度到哪裡。這比重新翻閱昨天的commit訊息更為有效。
編碼期間的研究捕捉
在實作功能時,無需離開編輯器即可捕捉洞察:
/capture "FastAPI dependency injection with async generators requires yield,
not return. The generator is the dependency lifecycle."
--domain programming
--tags fastapi,dependency-injection
捕捉到的洞察會立即被索引,供未來檢索使用。數月累積下來,這些微型捕捉將構築出一份實作層級的知識語料庫。
專案啟動
當開始一個新專案或新功能時:
- 搜尋vault:「我對[技術/模式]了解多少?」
- 檢視前5名結果,查看先前的決策與注意事項
- 檢查該領域是否已有MOC;若無,則建立一個
- 搜尋失敗模式:「problems with [technology]」
使用Vault搜尋進行除錯
遇到錯誤或非預期行為時:
Search my vault for [error message or symptom]
先前的除錯筆記通常已記錄根本原因與修正方法。這對跨專案反覆出現的問題特別有價值——vault會記得您遺忘的事。
程式碼審查準備
審查PR之前:
Search my vault for patterns and conventions about [module being changed]
Vault會傳回與被審查程式碼相關的先前決策、架構限制與編碼規範。審查過程因而得以奠基於制度性知識,而不僅僅是diff本身。
效能調校
本節涵蓋針對不同vault規模與使用模式的最佳化策略。
索引規模管理
| Vault規模 | Chunks | DB大小 | 完整重建索引 | 增量更新 |
|---|---|---|---|---|
| 500筆記 | ~1,500 | 3 MB | 15秒 | <1秒 |
| 2,000筆記 | ~6,000 | 12 MB | 45秒 | 2秒 |
| 5,000筆記 | ~15,000 | 30 MB | 2分鐘 | 4秒 |
| 15,000筆記 | ~50,000 | 83 MB | 4分鐘 | <10秒 |
| 50,000筆記 | ~150,000 | 250 MB | 15分鐘 | 30秒 |
當筆記達50,000以上時,請考慮: - 將batch size從64增加至128以加速嵌入 - 使用WAL模式(預設)以支援併發存取 - 在非尖峰時段執行完整重建索引
查詢最佳化
WAL模式。 SQLite的Write-Ahead Logging模式讓indexer寫入時仍能進行併發讀取:
db.execute("PRAGMA journal_mode=WAL")
當MCP伺服器處理查詢的同時indexer正執行增量更新,這點至關重要。
連線池。 MCP伺服器應重複使用資料庫連線,而非每次查詢都開啟新連線。搭配WAL模式的單一長生命週期連線即可支援併發讀取。
# MCP server initialization
db = sqlite3.connect(DB_PATH, check_same_thread=False)
db.execute("PRAGMA journal_mode=WAL")
db.execute("PRAGMA mmap_size=268435456") # 256 MB mmap
記憶體映射I/O。 mmap_size pragma會告訴SQLite對資料庫檔案使用記憶體映射I/O。對於一個83 MB的資料庫而言,將整個檔案映射進記憶體可消除大部分的磁碟讀取。
FTS5最佳化。 完整重建索引後,執行:
INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');
這會合併FTS5的內部b-tree分段,降低後續搜尋的查詢延遲。
擴展基準測試
於Apple M3 Pro、36 GB RAM、NVMe SSD上測量:
| 操作 | 500筆記 | 5K筆記 | 15K筆記 | 50K筆記 |
|---|---|---|---|---|
| BM25查詢 | 2ms | 5ms | 12ms | 25ms |
| 向量查詢 | 1ms | 3ms | 8ms | 20ms |
| RRF融合 | <1ms | <1ms | 3ms | 5ms |
| 完整搜尋 | 3ms | 8ms | 23ms | 50ms |
所有基準測試皆包含資料庫存取、查詢執行與結果格式化。MCP STDIO通訊的網路延遲會額外增加1至2ms。
疑難排解
索引漂移
症狀: 搜尋傳回過時的結果,或遺漏最近新增的筆記。
原因: 新增筆記後增量索引器未執行,或檔案的mtime未更新(例如,從另一台機器同步時保留了原始時間戳)。
修正方式: 執行完整重新索引:python index_vault.py --full
嵌入模型替換
症狀: 變更嵌入模型後,向量搜尋傳回毫無意義的結果。
原因: 舊的向量(來自先前模型)正在與新的查詢向量進行比對。維度或向量空間語義並不相容。
修正方式: 索引器應偵測到模型雜湊不符,並自動觸發完整重新索引。若未自動觸發,請手動清除資料庫並重新索引:
rm vectors.db
python index_vault.py --full
FTS5 維護
症狀: 經過多次增量更新後,FTS5查詢傳回不正確或不完整的結果。
原因: 經過多次小幅更新後,FTS5內部分段可能變得碎片化。
修正方式: 重建並最佳化:
INSERT INTO chunks_fts(chunks_fts) VALUES('rebuild');
INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');
MCP逾時
症狀: AI工具回報MCP伺服器已逾時。
原因: 第一次查詢會觸發模型載入(延遲初始化),這需要2-5秒。AI工具預設的MCP逾時時間可能較短。
修正方式: 在伺服器啟動時預先載入模型:
# In MCP server initialization
retriever = HybridRetriever(db_path, vault_path)
retriever.search("warmup", limit=1) # Trigger model load
SQLite檔案鎖定
症狀: 出現SQLITE_BUSY或SQLITE_LOCKED錯誤。
原因: 多個程序同時寫入資料庫。WAL模式允許並行讀取,但只允許一個寫入者。
修正方式: 確保只有一個程序(索引器)寫入資料庫。MCP伺服器與hooks應僅進行讀取。若需要並行寫入,請使用WAL模式並設定忙碌逾時:
db.execute("PRAGMA busy_timeout=5000") # Wait up to 5 seconds
sqlite-vec無法載入
症狀: 向量搜尋已停用;retriever僅在BM25模式下執行。
原因: sqlite-vec擴充功能未安裝、在函式庫路徑中找不到,或與SQLite版本不相容。
修正方式:
# Install via pip
pip install sqlite-vec
# Or compile from source
git clone https://github.com/asg017/sqlite-vec
cd sqlite-vec && make
驗證擴充功能是否成功載入:
import sqlite3
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
db.load_extension("vec0")
print("sqlite-vec loaded successfully")
大型vault記憶體問題
症狀: 對大型vault(50,000筆以上的筆記)執行完整重新索引時出現記憶體不足錯誤。
原因: 嵌入批次大小過大,或所有檔案內容同時載入記憶體。
修正方式: 縮減批次大小,並以增量方式處理檔案:
BATCH_SIZE = 32 # Reduce from 64
同時請確保索引器一次處理一個檔案(先讀取、chunking並嵌入每個檔案後再處理下一個),而非將所有檔案一次載入記憶體。
遷移指南
從Apple Notes遷移
- 透過「匯出全部」選項(macOS)匯出Apple Notes,或使用如
apple-notes-liberator等遷移工具 - 使用
markdownify或pandoc將HTML匯出檔轉換為markdown - 將轉換後的檔案移至vault的
00-inbox/資料夾 - 檢視並為每筆筆記新增frontmatter
- 將筆記移至適當的領域資料夾
從Notion遷移
- 從Notion匯出:Settings → Export → Markdown & CSV
- 將匯出的檔案解壓縮至vault的
00-inbox/資料夾 - 修正Notion特有的markdown殘留:
- Notion使用
- [ ]表示檢查清單——這是標準markdown - Notion以HTML形式包含屬性表格——請轉換為YAML frontmatter
- Notion以相對路徑嵌入圖片——請將圖片複製至附件資料夾
- 新增標準frontmatter(
type、domain、tags) - 將Notion頁面連結替換為Obsidian wiki-links
從Google Docs遷移
- 使用Google Takeout匯出所有文件
- 將
.docx檔案轉換為markdown:pandoc -f docx -t markdown input.docx -o output.md - 批次轉換:
for f in *.docx; do pandoc -f docx -t markdown "$f" -o "${f%.docx}.md"; done - 移至vault、新增frontmatter,並整理至各資料夾
從純markdown遷移(未使用Obsidian)
若您已有一個markdown檔案目錄:
- 將該目錄作為Obsidian vault開啟(Obsidian → Open Vault → Open folder)
- 若該目錄受版本控制,請將
.obsidian/加入.gitignore - 建立frontmatter範本並套用至既有檔案
- 在閱讀與整理筆記時,開始以
[[wiki-links]]連結各筆記 - 立即執行索引器——retrieval系統第一天就能運作
從其他retrieval系統遷移
若您正從其他嵌入/搜尋系統遷移:
- 不要嘗試遷移向量。 不同模型產生的向量空間並不相容。請使用新模型執行完整重新索引。
- 遷移的是內容,而非索引。 vault檔案才是真正的資料來源。索引只是衍生產物。
- 遷移後請進行驗證。 執行10-20個您已知答案的查詢,並驗證結果是否符合預期。
變更紀錄
| 日期 | 變更 |
|---|---|
| 2026-04-16 | Smart Connections v4.3.0(圖表檢視、可設定的dock、block-embedding復原、Substrate跨外掛環境)。記錄2026年4月AI原生外掛浪潮(Cortex、VaultSearch、LLM Wiki、Drift、EngramQuest、Hybrid Search MCP)。將MarkusPfundstein/mcp-obsidian標記為維護模式(最後一次commit為2025年6月)。Dataview處於休眠狀態;Bases為新工作的後繼者。Obsidian CLI 1.12.7仍是AI助理的首選橋接方案。 |
| 2026-04-01 | 新增Obsidian CLI章節(v1.12針對AI工作流程的指令)。新增agent外掛章節(Claudian、Agent Client)。記錄Bases核心外掛用於vault整理。將外掛數量更新為2,500+。新增iOS Share Extension作為輸入來源。在相容性矩陣中加入內嵌agent外掛。 |
| 2026-03-30 | MCPVault v0.11.0:list_all_tags工具、.base/.canvas支援、更名為@bitbonsai/mcpvault。Obsidian Desktop v1.12.7內建CLI執行檔,加快終端機互動速度。 |
| 2026-03-23 | 記錄sqlite-vec v0.1.7穩定版:vec0表的DELETE支援、用於分頁的KNN距離限制。DiskANN近似最近鄰索引已宣布將於後續版本推出。 |
| 2026-03-07 | 在嵌入模型比較中加入potion-multilingual-128M(101種語言,2025年5月)。sqlite-vec處於v0.1.7-alpha.10(CI/CD修正,無功能變更)。MCP規範與retrieval技巧確認為最新狀態。 |
| 2026-03-03 | 更新MCP規範演進(2025年11月已推出:Streamable HTTP、.well-known、工具註解)。新增Model2Vec微調與BPE/Unigram tokenizer支援。新增社群MCP伺服器比較表。將Smart Connections更新至v4。 |
| 2026-03-02 | 在模型比較中加入potion-base-32M與potion-retrieval-32M。新增量化/降維章節。新增MCP規範演進說明。 |
| 2026-03-01 | 初次發布 |
參考資料
-
Internet Vin, “22 commands I use with Obsidian and Claude Code,” March 2026, x.com/internetvin/status/2026461256677245131. ↩
-
Nicopreme, “Visual Explainer” agent skill with slash commands, x.com/nicopreme/status/2023495040258261460. ↩
-
Cormack, G.V., Clarke, C.L.A., and Buettcher, S. Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods. SIGIR, 2009. 介紹以 k=60 作為參數的 RRF,為合併排序清單的無參數方法。 ↩↩↩
-
OpenAI Embeddings Pricing. text-embedding-3-small:每百萬 tokens 0.02 美元。估算 vault 完整重建索引成本約為 0.30 美元。 ↩
-
van Dongen, T. et al. Model2Vec: Turn any Sentence Transformer into a Small Fast Model. arXiv, 2025. 描述從 sentence transformers 蒸餾產生靜態 embeddings 的方法。 ↩
-
MTEB: Massive Text Embedding Benchmark. potion-base-8M 平均分數 50.03,對比 all-MiniLM-L6-v2 的 56.09(保留率 89%)。 ↩
-
SQLite FTS5 Extension. FTS5 提供具 BM25 排名與可設定欄位權重的全文搜尋功能。 ↩
-
sqlite-vec: A vector search SQLite extension. 在 SQLite 內提供
vec0虛擬資料表以執行 KNN 向量搜尋。 ↩ -
Robertson, S. and Zaragoza, H. The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval, 2009. ↩
-
Karpukhin, V. et al. Dense Passage Retrieval for Open-Domain Question Answering. EMNLP, 2020. 在開放領域 QA 上,密集表徵比 BM25 的表現高出 9-19%。 ↩
-
Reimers, N. and Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. EMNLP, 2019. 密集語義相似度的奠基性研究。 ↩
-
Luan, Y. et al. Sparse, Dense, and Attentional Representations for Text Retrieval. TACL, 2021. 在 MS MARCO 上,hybrid retrieval 始終優於單一模態方法。 ↩
-
SQLite Write-Ahead Logging. WAL 模式支援多方並發讀取搭配單一寫入者。 ↩
-
Gao, Y. et al. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv, 2024. RAG 架構與 chunking 策略綜述。 ↩
-
Thakur, N. et al. BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models. NeurIPS, 2021. ↩
-
Model2Vec: Distill a Small Fast Model from any Sentence Transformer. Minish Lab, 2024. ↩
-
Obsidian Documentation. Obsidian 官方文件。 ↩
-
Model Context Protocol Specification. 連結 AI 工具與資料來源的 MCP 標準。 ↩
-
作者的實際生產資料。16,894 個檔案、49,746 個 chunks、83.56 MB SQLite 資料庫,14 個月內處理 7,771 筆訊號。查詢延遲以
time.perf_counter()測量。 ↩ -
Model2Vec Potion Models. Minish Lab, 2025. Potion-base-32M(MTEB 52.46)、potion-retrieval-32M(MTEB retrieval 36.35),以及 v0.5.0+ 的量化/降維功能。 ↩↩↩
-
Update on the Next MCP Protocol Release. 2025 年 11 月版本推出 Streamable HTTP 傳輸、.well-known URL 探索、結構化工具註解,以及 SDK 層級標準化。下一版暫定 2026 年年中推出,將新增非同步操作、領域特定擴充功能,以及 agent 之間的通訊。 ↩↩
-
Model2Vec Releases. v0.4.0(2025 年 2 月):支援訓練/微調。v0.5.0(2025 年 4 月):後端重寫、量化、降維。v0.7.0(2025 年 10 月):詞彙量化,支援 BPE/Unigram tokenizer。 ↩↩
-
Smart Connections for Obsidian. Smart Connections v4:本地優先的 AI embeddings,初次建立索引後語義搜尋可離線運作。 ↩
-
potion-multilingual-128M. Minish Lab, 2025 年 5 月。支援 101 種語言的靜態 embedding 模型,是表現最佳的多語言靜態 embeddings。與其他 potion 模型相同,僅需 numpy 相依。 ↩
-
MCPVault v0.11.0. 2026 年 3 月。新增
list_all_tags工具,可掃描 frontmatter 與 hashtag 並計算次數。改善點狀資料夾處理,支援.base與.canvas檔案。npm 套件更名為@bitbonsai/mcpvault。 ↩ -
sqlite-vec v0.1.7 Release. 2026 年 3 月 17 日。穩定版:vec0 虛擬資料表支援 DELETE、KNN 距離約束可用於分頁、模糊測試改進。宣布 DiskANN 近似最近鄰索引將於未來版本推出。 ↩↩↩
-
Introduction to Bases. Obsidian v1.9.10 引入的核心外掛。以 frontmatter 屬性作為欄位,為 vault 檔案提供類資料庫檢視(表格、畫廊、行事曆、看板)。檔案以
.base格式儲存。 ↩ -
Obsidian 1.12 Desktop Changelog. 2026 年 2 月 27 日。引入 Obsidian CLI,用於終端機式 vault 自動化。指令包括搜尋、每日筆記、範本、屬性、外掛、任務與開發者工具。CLI documentation. ↩
-
Claudian. 將 Claude Code 以 AI 協作者身分嵌入 vault 的 Obsidian 外掛。提供側邊欄對話、情境感知提示、視覺支援、slash 指令與權限模式。 ↩
-
Agent Client. Obsidian 外掛,透過 Agent Client Protocol(ACP)為 Claude Code、Codex CLI 與 Gemini CLI 提供統一介面。支援筆記提及、shell 執行與動作核准。 ↩
-
Obsidian iOS Changelog. 2026 年初的更新包含 Share Extension(可將其他應用程式的內容直接儲存至 vault)、Daily Note 與 Bookmark 小工具修正,以及 View Note 小工具重新整理改善。 ↩
-
MarkusPfundstein/mcp-obsidian. 最後一次 commit 為 2025 年 6 月 28 日。未發布任何標記版本。論壇討論(2026 年 4 月)指出,自 Obsidian 1.12.x 推出第一方 CLI 後,社群已逐步轉向以 CLI 為基礎的整合方式。本指南為歷史脈絡與既有使用者保留此條目,但不建議用於新部署。 ↩↩
-
Smart Connections v4.3.0 Release. 2026 年 4 月 1 日。自 4.1.8(2026 年 1 月)以來的第一個重大版本。主要功能:連結清單的圖表檢視(可切換、可設定預設值)、connections-view 開啟位置可自訂(寫作區旁停駐、側邊工作區、自訂)、中斷執行後的 block-embedding 還原改善,以及跨外掛的「Substrate」環境,可在 Smart Connections、Smart Chat 與 Smart Composer 之間共享狀態。 ↩