obsidian:~/vault$ search --hybrid obsidian

Obsidian MCP+混合检索:2026参考

# 使用官方Obsidian文档了解应用基础;使用Blake的参考资料了解MCP、混合搜索,以及如何为包含16,894个文件的AI知识库建立索引。

words: 2857 read_time: 51m updated: 2026-05-29 09:48
$ retriever search --hybrid obsidian

Obsidian不是笔记应用。它是一个本地优先、纯文本、图结构化的Markdown语料库;加入检索基础设施后,它会成为AI上下文储备池。16,894个文件。49,746个分块。23ms查询。0次API调用。1个83 MB的SQLite文件。本指南涵盖完整系统:从知识库架构到hybrid检索,再到MCP集成与运营工作流。


关键要点

这是上下文工程,不是记笔记。 Obsidian库对AI的价值不在笔记本身,而在让笔记可查询的检索层。没有检索能力的16,000文件库,只是一个只写数据库。一个包含200个文件、具备hybrid混合搜索和MCP集成的库,才是AI知识库。检索基础设施才是产品,笔记只是原材料。

混合检索胜过纯关键词或纯语义搜索。 BM25能捕捉精确标识符和函数名。向量搜索能跨不同术语捕捉同义词和概念匹配。Reciprocal Rank Fusion(RRF)可以合并二者,而且无需校准分数。单独使用任一方法,都无法覆盖这两类失败模式。关于MS MARCO段落排序的研究证实了这一规律:混合检索始终优于单独使用任一方法。3 混合检索器深度解析介绍了RRF数学原理、带真实数字的示例、失败模式分析,以及一个交互式融合计算器。

MCP让AI工具能够直接访问库。 Model Context Protocol(MCP)服务器将检索器暴露为工具,供Claude Code、Codex CLI、Cursor和其他AI工具直接调用。agent查询库,接收带有来源归属的排序结果,并在不加载完整文件的情况下使用上下文。MCP服务器只是检索引擎外的一层轻量封装。

本地优先意味着零API成本和完整隐私。 整套栈都在单台机器上运行:SQLite负责存储,Model2Vec负责嵌入,FTS5负责关键词搜索,sqlite-vec负责向量KNN。不使用云服务,不调用API,也不依赖网络。个人笔记不会离开本机。按OpenAI API价格计算,对49,746个块进行完整重新嵌入大约只需0.30美元,但真正的成本在于延迟、隐私暴露,以及一个本应离线工作的系统对网络产生依赖。4

增量索引让系统在10秒内保持最新。 文件修改时间比较可以检测变更。只有修改过的文件会重新chunking(切块)并重新嵌入。在Apple M系列硬件上,完整重建索引大约需要4分钟。典型一天编辑产生的增量更新可在10秒内完成。系统无需人工干预,也能保持最新状态。

该架构可从200篇笔记扩展到20,000+篇笔记。 同一套三层设计(摄取、检索、集成)适用于任何规模的库。从小型库上的BM25-only搜索开始。当关键词冲突成为问题时,再添加向量搜索。需要同时获得精确匹配和语义匹配时,再添加RRF融合。每一层都可以独立发挥作用,也可以独立移除。


如何使用本指南

本指南涵盖完整系统。您的起点取决于当前阶段:

您的情况是… 从这里开始 然后继续探索
刚开始接触Obsidian + AI 为什么选择Obsidian作为AI基础设施快速开始 库架构MCP服务器架构
已有库,希望接入AI MCP服务器架构Claude Code集成 嵌入模型全文搜索
正在构建检索系统 完整检索流水线Reciprocal Rank Fusion 性能调优故障排查
团队或企业场景 决策框架知识图谱模式 开发者工作流配方迁移指南

标记为Contract的章节包含实现细节、配置块和失败模式。标记为Narrative的章节侧重概念、架构决策以及设计选择背后的推理。标记为Recipe的章节提供分步工作流。


为什么选择Obsidian作为AI基础设施

本指南的核心论点是:Obsidian库是个人AI知识库的最佳基底,因为它本地优先、基于纯文本、具备图结构,并且用户可以控制整套栈的每一层。

Obsidian能为AI提供什么,而其他替代方案不能

纯文本markdown文件。 每篇笔记都是文件系统中的一个.md文件。没有专有格式,不需要数据库导出,也不需要API即可读取内容。任何能读取文件的工具,都能读取您的库。grepripgrep、Python的pathlib、SQLite FTS5——它们都可以直接作用于源文件。构建检索系统时,索引的是文件,而不是API响应。索引始终与源保持一致,因为源就是文件系统。

本地优先架构。 库位于您的机器上。没有服务器,没有云同步依赖,没有API速率限制,也没有服务条款来规定您如何处理自己的内容。无需任何外部服务,就能对笔记进行嵌入、索引、chunking和搜索。这对AI基础设施很重要,因为检索流水线的速度取决于磁盘,而不是API端点的响应速度。它对隐私同样重要:包含凭据、健康数据、财务信息和私人思考的个人笔记永远不会离开本机。

通过wiki-link形成图结构。 Obsidian的[[wiki-link]]语法会在笔记之间创建有向图。一篇关于OAuth实现的笔记,会链接到关于令牌轮换、会话管理和API安全的笔记。图结构编码了人与概念之间经过人工整理的关系。向量嵌入能捕捉语义相似性,但wiki-link捕捉的是作者在思考相关主题时主动建立的连接。这种图信号是嵌入无法复制的。

插件生态系统。 Obsidian拥有2,500+个社区插件(截至2026年3月,高于2025年中期的1,800+)。Dataview可以像查询数据库一样查询您的库。Templater用JavaScript逻辑从模板生成笔记。Git集成可以把您的库同步到仓库。Linter用于强制保持格式一致。Bases核心插件(在v1.9.10中引入)基于frontmatter属性作为字段,为库文件添加类似数据库的视图——表格、画廊、日历和看板,并保存为.base文件。15 这些插件在不改变底层纯文本格式的前提下,为库增加结构。检索系统索引的是这些插件的输出,而不是插件本身。

5百万+用户。 Obsidian拥有庞大而活跃的社区,持续产出模板、工作流、插件和文档。当您遇到库组织或插件配置问题时,很可能已经有人记录了解决方案。社区也在产出Obsidian周边工具:MCP服务器、索引脚本、发布流水线和API封装器。

仅靠文件系统无法提供什么

markdown文件目录具备纯文本优势,但缺少Obsidian增加的3项能力:

  1. 双向链接。 Obsidian会自动跟踪backlink。当您从Note A链接到Note B时,Note B会显示Note A引用了它。图面板可以可视化连接集群。这种双向感知是一种元数据,原始文件系统并不提供。

  2. 带插件渲染的实时预览。 Dataview查询、Mermaid图表和callout块都会实时渲染。写作体验比文本编辑器更丰富,同时存储格式仍保持纯文本。您在丰富的环境中写作和组织;检索系统索引原始markdown。

  3. 社区基础设施。 插件发现、主题市场、同步服务(可选)、发布服务(可选)以及文档生态。您可以用独立工具复刻任何单项功能,但Obsidian将它们打包成一个连贯的工作流。

Obsidian不会做什么(以及您需要构建什么)

Obsidian不包含检索基础设施。它有基础搜索(全文、文件名、标签),但没有嵌入流水线、向量搜索、融合排序、MCP服务器、凭据过滤、chunking策略,也没有面向外部AI工具的集成钩子。本指南涵盖的是您在Obsidian之上构建的基础设施。 库是基底。检索流水线、MCP服务器和集成钩子才是基础设施。

这里描述的架构是markdown-first,而非Obsidian-exclusive。 如果您使用Logseq、Foam、Dendron,或只是一个普通的markdown文件目录,检索流水线也能以同样方式工作。chunker读取.md文件。embedder处理文本字符串。indexer写入SQLite。这些组件都不依赖Obsidian特有功能。Obsidian的贡献在于提供写作和组织环境,产出供检索器索引的markdown文件。


快速开始:第一个接入AI的库

本节将在5分钟内把一个库连接到AI工具。您将安装Obsidian、创建库、安装MCP服务器,并运行第一次查询。快速开始使用社区MCP服务器,以便立即看到结果。后续章节会介绍如何为生产使用构建自定义检索管线。

前提条件

  • macOS、Linux或Windows
  • Node.js 18+(用于MCP服务器)
  • Obsidian 1.12+(用于CLI集成;更早版本也可用于仅MCP的配置)
  • 已安装Claude Code、Codex CLI或Cursor

步骤1:创建库

obsidian.md下载Obsidian并创建新库。选择一个您记得住的位置——MCP服务器需要绝对路径。

# Example vault location
~/Documents/knowledge-base/

添加几篇笔记,让检索器有内容可查。即使只有10-20篇笔记,也足以看到效果。每篇笔记都应是.md文件,标题应有明确含义,并至少包含一段正文内容。

步骤2:安装MCP服务器

多个社区MCP服务器都能提供即时的库访问能力。这个生态在2025-2026年间显著发展。近期值得注意的更新包括MCPVault v0.11.0(2026年3月),它新增了list_all_tags,可扫描frontmatter和标签并统计数量,改进了带点文件夹的处理,并支持.base.canvas文件。13该包在npm上也已更名为@bitbonsai/mcpvault

2026年4月转向——Obsidian CLI成为首选桥接方式:Obsidian 1.12.0引入了一等公民式的CLI,公开发布的1.12.7安装器(2026年3月23日)则捆绑了独立二进制文件、TUI和socket文件改进,让终端工作流更易安装和运行。16社区工具正在积极从Local REST API插件(它曾为mcp-obsidian提供动力)迁移到基于CLI的集成,因为它更快也更稳定。MarkusPfundstein/mcp-obsidian仓库自2025年6月以来没有提交,也从未发布过标签版本——应将其视为维护模式,并优先选择基于CLI的服务器或下方列出的较新社区替代方案。20推荐配置请参阅本指南后面的“面向AI工作流的Obsidian CLI”一节。

服务器 作者 传输方式 是否需要插件 关键功能
obsidian-mcp-server StevenStavrakis STDIO 轻量级,基于文件
mcp-obsidian MarkusPfundstein STDIO Local REST API 通过REST实现完整库CRUD——维护模式,自2025年6月以来无提交20
obsidian-mcp-tools jacksteamdev STDIO 是(插件) 语义搜索+Templater
obsidian-claude-code-mcp iansinnott WebSocket 是(插件) 面向Claude Code的自动发现
obsidian-mcp-server cyanheads STDIO Local REST API 标签、frontmatter管理
Hybrid Search MCP 社区 STDIO BM25+语义搜索MCP服务器+CLI。截至2026年4月仍较新且活跃维护。

对于快速开始,最简单的选择是直接读取.md文件的基于文件的服务器:

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工具,并提出一个可由库中笔记回答的问题:

Search my Obsidian vault for notes about [topic you wrote about]

AI工具会调用MCP服务器,由它搜索您的库并返回匹配内容。您应该会看到包含文件路径和相关摘录的结果。

您刚刚构建了什么

您通过标准协议把本地知识库连接到了AI工具。MCP服务器读取库文件,执行基础搜索,并返回结果。这就是最小可行版本。

这个快速开始不包含: - hybrid检索(BM25+向量搜索+RRF融合) - 基于embeddings(嵌入向量)的语义搜索 - 凭据过滤 - 增量索引 - 基于hook的自动上下文注入

本指南其余部分将介绍如何逐一构建这些能力。快速开始用于验证概念,完整管线则提供生产级检索能力。


面向AI工作流的Obsidian CLI

Obsidian 1.12(2026年2月)引入了内置命令行界面,为AI工作流打开了新的集成入口。16CLI相当于Obsidian GUI的远程控制器——Obsidian必须正在运行(或在首次命令执行时自动启动)。可在Settings > General > Command line interface中启用。

为什么CLI对AI基础设施很重要

CLI提供了对Obsidian原生操作的程序化访问,而这些操作过去需要GUI或插件API才能完成。对于AI工作流,关键能力包括:

  • 从脚本和hook中搜索。obsidian search "query"obsidian search:context "query"可从任意shell脚本、hook或自动化管线运行库搜索。search:context变体会返回匹配行及其周边上下文,适合把结果输入AI提示。
  • 每日笔记自动化。obsidian daily会打开或创建今天的每日笔记。结合shell脚本后,可实现自动化每日简报工作流——hook可以把AI生成的摘要追加到每日笔记中。
  • 基于模板创建笔记。obsidian template listobsidian template create可从Templater或核心模板生成笔记,使AI agent无需直接写入markdown文件也能创建结构化库条目。
  • 属性管理。obsidian property setobsidian property get可读取和写入frontmatter属性,让脚本无需解析YAML即可更新元数据。
  • 插件控制。obsidian plugin enable/disable/list可通过程序管理插件,适合在批量操作期间切换索引插件。
  • 任务管理。obsidian task list/add/complete提供结构化任务访问能力,适合管理库中工作事项的AI agent使用。

用于AI访问的CLI与MCP

CLI和MCP服务器承担不同角色。二者相辅相成,并非相互竞争:

方面 Obsidian CLI MCP服务器
调用方 Shell脚本、hook、cron任务 AI agent(Claude Code、Codex、Cursor)
协议 POSIX进程(stdin/stdout/stderr) MCP(通过STDIO或HTTP承载JSON-RPC)
优势 Obsidian原生操作(模板、插件、属性) 自定义检索(embeddings、BM25、RRF融合)
限制 无向量搜索,无embedding管线 无法访问Obsidian内部操作
最适合 自动化脚本、采集管线、hook动作 会话期间的实时AI agent查询

建议:将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智能体插件

越来越多的Obsidian插件将AI编码智能体直接嵌入知识库UI,为外部MCP服务器配置提供了另一种选择。这些插件在Obsidian的侧边栏内运行AI智能体,而不是从外部工具连接。

Claudian

Claudian将Claude Code作为AI协作者嵌入知识库。知识库目录会成为Claude的工作目录,使其具备完整的智能体能力:文件读写、搜索、bash命令和多步骤工作流。17

AI基础设施的关键功能: - 上下文感知提示。 自动附加当前聚焦的笔记,支持@notename文件提及、基于标签的排除,以及将编辑器选区作为上下文。 - 视觉支持。 通过拖放、粘贴或文件路径分析图像,适合处理知识库中保存的截图和图表。 - Slash commands。 创建由/command触发的可复用提示模板,用于标准化知识库操作。 - 权限模式。 YOLO(自动批准)、Safe(逐项批准)和Plan(仅计划)模式,并配有安全阻止列表和知识库范围限制。

Agent Client

Agent Client通过Agent Client Protocol (ACP),将Claude Code、Codex CLI和Gemini CLI整合到统一的Obsidian侧边栏中。18

关键功能: - 多智能体切换。 在同一面板中与Claude Code、Codex或Gemini CLI聊天,并可按需在智能体之间切换。 - 笔记提及。 使用@notename将笔记内容纳入提示,类似Claudian,但不绑定特定智能体。 - Shell执行。 在聊天中内联执行终端命令,包括构建脚本、git命令或任何终端操作,无需离开对话。 - 操作批准。 对文件读取、编辑和命令执行进行细粒度控制。

何时使用智能体插件,何时使用外部MCP

场景 智能体插件 外部MCP
在AI辅助下撰写和编辑知识库笔记 更好——智能体能看到编辑器上下文 可用,但不具备编辑器感知能力
跨多个仓库进行代码开发 有限——仅限知识库范围 更好——以项目为范围,并可访问完整文件系统
从大型索引语料库中检索 仅基础搜索 完整的hybrid检索流水线
记笔记过程中快速进行知识库问答 理想——无需切换上下文 需要切换到终端

建议: 将智能体插件用于以知识库为中心的工作流(撰写、整理、总结笔记)。将外部MCP服务器用于开发工作流,在这些场景中,AI智能体需要完整的检索流水线,并需要访问知识库之外的代码库。这两种方式可以并存——在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-link) 部分(提及)
AI可索引 直接文件访问 需要API 需要导出 直接文件访问 已在上下文中
插件生态 2,500+个插件 集成 N/A N/A
离线能力 完整 缓存只读 部分 完整 完整
扩展到10K+笔记 是(配合API) 会退化 否(单文件)
成本 免费(核心功能) $10/月起 免费 免费 免费

何时Obsidian显得过度

  • 单项目上下文。 如果AI只需要当前代码库的上下文,请将其放在CLAUDE.mdAGENTS.md或项目级文档中。这些文件会随仓库一起流转,并会自动加载。
  • 结构化数据。 如果内容是表格、记录或schema,请使用数据库。Obsidian笔记以散文文本为主。Dataview可以查询frontmatter字段,但真正的数据库更适合处理结构化查询。
  • 临时研究。 如果笔记会在项目结束后丢弃,带有markdown文件的临时目录更简单。不要为转瞬即逝的内容构建检索基础设施。

何时Obsidian是正确选择

  • 跨数月或数年积累知识。 随着语料库增长,价值会复利累积。一个每天查询、持续使用6个月的200篇笔记知识库,比一个只查询一次的5,000篇笔记知识库更有价值。
  • 一个语料库覆盖多个领域。 如果知识库包含编程、架构、安全、设计和个人项目等笔记,跨领域检索会带来项目专属CLAUDE.md无法提供的价值。
  • 隐私敏感内容。 本地优先意味着检索流水线绝不会将内容发送到外部服务。知识库会包含您放入其中的任何内容,包括您不愿上传到云服务的内容。

心智模型:三层

该系统有三层,它们可以独立运行,但组合后会相互增强。每一层关注点不同,失败模式也不同。

┌─────────────────────────────────────────────────────┐
                 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        
└─────────────────────────────────────────────────────┘

摄取决定哪些内容进入知识库。如果缺少筛选,知识库会不断积累噪音:推文截图、没有注释的复制粘贴文章、缺乏上下文的半成品想法。摄取层负责在入口处进行质量控制。评分流水线、标签约定或人工审查流程都可以,只要机制能够确保知识库中包含值得检索的内容。

检索让知识库可查询。这是引擎:将笔记chunking(分块)为搜索单元,将分块转换为embeddings(嵌入)并放入向量空间,为关键词搜索和语义搜索建立索引,再用RRF融合结果。检索层将一个文件目录转化为可查询的知识库。没有这一层,知识库仍可通过手动浏览和基础搜索导航,但无法以编程方式供AI工具访问。

集成将检索层连接到AI工具。MCP服务器将检索能力暴露为可调用工具。Hooks自动注入上下文。Skills将新知识捕获回知识库。集成层是知识库与使用它的AI智能体之间的接口。

这些层在设计上彼此解耦。摄取评分流水线不需要了解embeddings。检索器不需要了解信号路由规则。MCP服务器不需要了解笔记如何创建。这种解耦意味着您可以独立改进任意一层。替换嵌入模型而不改变摄取流水线。新增MCP能力而无需修改检索器。调整信号评分启发式规则而不触碰索引。


供 AI 使用的 Obsidian 库架构

针对 AI 检索优化的 Obsidian 库,与针对个人浏览优化的库遵循不同约定。本节介绍文件夹结构、笔记 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 正文的内容,包括项目、领域、资源、信号和日记。

应排除在索引之外的文件夹:模板(其中包含占位变量,而非内容)、附件(二进制文件)、Obsidian 配置,以及任何包含敏感内容且不希望进入检索索引的文件夹。

.indexignore 文件

在库根目录创建 .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/

索引器会在扫描前读取此文件,并完全跳过匹配的路径。被排除路径中的文件不会被 chunking,不会生成 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 — 支持按类型过滤的查询(“只显示 MOC”或“只显示 signals”)
  • tags — 以 0.3 权重索引到 FTS5 标题上下文中,即使正文使用了不同术语,也能提供关键词匹配

可选但很有价值的字段:

  • domain — 支持按领域限定的查询(“仅搜索安全笔记”)
  • source — 为采集内容提供归因;检索器可以在结果中包含来源 URL
  • status — 允许从活跃搜索中排除已归档或草稿笔记

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...

3 个 H2 小节会生成 3 个可独立搜索的 chunks。每个 chunk 都有足够的上下文,让 embedding 能够捕捉其含义。关于“过期 token 处理”的查询会精准匹配第 3 个 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 会处理其余部分。

不应放入笔记的内容

会降低检索质量的内容:

  • 未经标注地原样粘贴整篇文章。检索器会索引原文章的关键词,使库中充斥并非您亲自撰写的内容,从而稀释检索质量。建议改为添加摘要、提取要点,或链接到来源 URL。
  • 没有文字说明的截图。检索器索引的是 markdown 文本。没有 alt 文本或周边说明的图片,对 BM25 和向量搜索都是不可见的。
  • 凭据字符串。API keys、tokens、passwords、connection strings。即使有凭据过滤,最稳妥的做法仍是永远不要把密钥粘贴到笔记中。请改用名称引用(例如“~/.env 中的 Cloudflare API token”)。
  • 未经整理的自动生成内容。如果某个工具生成了笔记(会议转录、Readwise highlights、RSS import),请先审阅并添加注释,再让它进入永久库。未经整理的自动导入只会增加体量,而不会增加可检索价值。

AI工作流的 Plugin生态系统

能够提升AI检索库质量的Obsidian plugins可分为三类:结构类(强制一致性)、查询类(暴露元数据)和同步类(保持库内容最新)。

必备 Plugins

Dataview。 使用frontmatter字段像查询数据库一样查询您的库。创建动态索引:“过去30天内更新、标记为security的所有笔记”,或“状态为active的所有项目笔记”。Dataview不会直接帮助检索,但它能帮助您发现库覆盖范围中的缺口,并找出需要更新的笔记。

TABLE type, domain, updated
FROM "03-resources"
WHERE status = "active"
SORT updated DESC
LIMIT 20

Templater。 基于模板创建带有动态字段的笔记。通过使用预填createdtypedomain字段的模板,确保每篇新笔记都以正确的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

## References

Linter。 在整个库中强制执行格式规则。一致的标题层级(H1用于标题,H2用于章节,H3用于小节)可确保分块器(chunker)生成可预测的结果。对检索有影响的Linter规则包括:

  • 标题递增:强制标题级别按顺序排列(不要从H1跳到H3)
  • YAML标题:与文件名匹配
  • 行尾空格:移除(避免FTS5分词伪影)
  • 连续空行:限制为1行(分块更干净)

Git integration。 为您的库提供版本控制。跟踪随时间变化的内容,在多台机器之间同步,并从误删中恢复。Git还提供索引器用于增量变更检测的mtime数据。

有助于索引的 Plugins

Smart Connections。 一个Obsidian plugin,在Obsidian内部提供AI驱动的语义搜索。Smart Connections v4默认创建本地embeddings(嵌入向量)——一旦您的库完成索引,语义连接和查找就可以完全离线运行,不会调用API。11 v4.5.0(2026年5月5日)将页脚连接纳入Smart Connections Core,因此每个安装版本都能在页脚显示相关笔记连接,无需打开侧边面板。近期v4版本还为连接列表添加了图谱视图、可配置的停靠位置、索引中断后改进的块embedding恢复,以及“Substrate”——一个跨plugin环境,让Smart Connections、Smart Chat和Smart Composer能够共享状态。21虽然本指南中的检索系统位于Obsidian外部(作为Python流水线运行),但Smart Connections有助于在写作时探索语义关系。两个系统索引相同内容,但服务于不同用例:Smart Connections用于编辑器内发现,外部检索器则通过MCP集成AI工具。

2026年4月发布的AI原生 plugins。 一批新的社区plugins直接面向Claude Code/Codex/Gemini-CLI工作流:

Plugin 发布时间 功能
Cortex 4月4日 由Claude Code驱动的库代理——将库视为代理工作区,而不只是笔记存储
VaultSearch 4月7日 本地优先的hybrid混合搜索:BM25+语义+模糊搜索(与本指南的检索栈直接重叠)
LLM Wiki 4月9日 将您的库转变为可私密查询的知识库
Drift 4月11日 面向AI驱动Obsidian编辑的VS Code风格差异查看器;定位于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查询plugin)上次发布0.5.70是在2025年4月,此后基本处于停滞状态。对于新工作,Obsidian内置的Bases功能(1.9+)是事实上的继任者,也是推荐路径。

Metadata Menu。 提供结构化frontmatter编辑,并为字段值提供自动补全。减少typedomaintags字段中的拼写错误。一致的元数据可提升检索过滤的准确性。

会损害索引的 Plugins

Excalidraw。 将绘图作为嵌入在markdown文件中的JSON存储。该JSON在语法上是有效的markdown,但分块并嵌入后会产生垃圾内容。请通过.indexignore将Excalidraw文件排除在索引之外,或按文件扩展名过滤。

Kanban。 将看板状态存储为特殊格式的markdown。该格式是为Kanban渲染设计的,不适合散文式内容检索。分块器会生成卡片标题和元数据片段,这些内容的嵌入效果并不好。请将Kanban看板排除在索引之外。

Calendar。 创建内容极少的每日笔记(通常只有日期标题)。空笔记或近乎空白的笔记会产生低质量分块。如果使用每日笔记,请在其中写入实质内容,或将每日笔记文件夹排除在索引之外。

重要的 Plugin配置

文件恢复→启用。 防止意外删除笔记。虽然与检索没有直接关系,但对于您所依赖的知识库至关重要。

严格换行→禁用。 符合Markdown标准的换行(用双换行表示段落)比Obsidian的严格模式(用单换行表示<br>)能产生更干净的分块。

默认新文件位置→指定文件夹。 将新文件路由到00-inbox/,避免未分类笔记污染领域文件夹。收件箱是暂存区;文件经过分拣后再移动到领域文件夹。

Wiki-link格式→尽可能使用最短路径。 更短的链接目标更便于检索器在索引链接结构时解析。


Embedding 模型:选择与配置

Embedding 模型会将文本块转换为数值向量,用于语义搜索。模型选择会决定检索质量、索引大小、嵌入速度以及运行时依赖项。本节说明为什么默认选择 Model2Vec 的 potion-base-8M,以及何时选择其他方案。

为什么选择 Model2Vec potion-base-8M

模型:minishlab/potion-base-8M 参数量:760万 维度:256 大小:约30 MB 依赖项:model2vec(仅需 numpy,无需 PyTorch) 推理:仅 CPU,静态词嵌入(无注意力层)

Model2Vec 会将句子转换器的知识蒸馏到静态 token 嵌入中。它不像 BERT、MiniLM 和其他 transformer 模型那样在输入上运行注意力层,而是通过对预先计算的 token 嵌入进行加权平均来生成向量。5实际影响是:由于没有顺序计算,嵌入速度比基于 transformer 的模型快 50 到 500 倍。

在当前的 Model2Vec 结果页面上,potion-base-8M 的全任务得分约为 all-MiniLM-L6-v2 的 92%(51.32 对 55.80),同时速度快出数个数量级。6剩余的质量差距,是换取速度和简洁性优势所付出的代价。对于较短的 markdown 块(典型知识库中平均 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 的回退模式运行时(例如未安装嵌入 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 51.32 默认:本地、快速、无需 GPU
potion-base-32M 256 120 MB 400x 52.83 更高质量,仍为静态模型
potion-retrieval-32M 256 120 MB 400x 35.06(检索) 面向检索优化的静态模型
potion-multilingual-128M 256 约500 MB 300x 多语言知识库(101种语言)
all-MiniLM-L6-v2 384 80 MB 1x 55.80 更高质量,仍可本地运行
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-8M 更好的质量,同时仍留在静态嵌入系列中,请选择 potion-base-32M。它使用从 baai/bge-base-en-v1.5 蒸馏出的更大词表,达到 52.83 的全任务得分(比 potion-base-8M 高约 3%),同时保持相同的 256 维输出和仅依赖 numpy 的特性。8模型文件增大 4 倍会提升内存占用,但嵌入速度仍比 transformer 模型快数个数量级。

如果主要用例是检索(知识库搜索正是如此),请选择 potion-retrieval-32M。此变体专门针对检索任务从 potion-base-32M 微调而来,在 Model2Vec 的检索基准表中得分 35.06,而 potion-base-32M 为 32.67。8取舍在于,它针对检索进行了优化,而不是追求通用嵌入质量。

如果知识库包含多种语言的笔记,请选择 potion-multilingual-128M。这个支持 101 种语言的模型于 2025 年 5 月发布,是多语言任务中表现最好的静态嵌入模型。它可以为任意语言的文本生成嵌入,同时保持与其他 potion 模型相同的仅 numpy 依赖。12较大的模型文件(约500 MB)是获得跨语言能力的代价。当您的笔记既包含英文内容,也包含日语、中文、德语或其他非英语内容时,建议使用它。

当检索质量比速度更重要,并且您已安装 PyTorch 时,请选择 all-MiniLM-L6-v2。与 256 维向量相比,384 维向量会让 SQLite 数据库大小增加约 50%。在 M 系列硬件上,对 15,000 个文件执行完整重建索引时,嵌入速度会从不到 1 分钟降至约 10 分钟。

当需要尽可能高的本地检索质量,并且可以接受较慢的索引速度时,请选择 nomic-embed-text-v1.5。768 维向量大约会让数据库大小增加到 3 倍。需要 PyTorch 以及现代 CPU 或 GPU。

当可以接受网络延迟和隐私取舍时,请选择 text-embedding-3-small。API 会生成最高质量的嵌入,但也会引入云端依赖、按 token 计费(每百万 token 0.02 美元),并将您的内容发送到 OpenAI 服务器。

其他情况下请继续使用 potion-base-8M。速度优势对迭代式索引至关重要(开发期间重新索引),仅依赖 numpy 可避免 PyTorch 安装复杂性,256 维向量也能让数据库保持紧凑。

量化与降维

Model2Vec v0.5.0+ 支持以较低精度和较少维度加载模型。8这对于在受限硬件上部署,或在不切换模型的情况下减小数据库大小很有用:

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 可以让向量存储减半,同时质量损失很小。

Model2Vec v0.8.x 更新了 tokenizer/持久化内部机制,弃用 Python 3.9 支持,并将已发布结果刷新到较新的 MTEB 表。升级生产 indexer 前,请固定或测试 model2vec 版本,因为即使嵌入模型名称不变,库升级也可能改变模型加载路径。10

针对知识库的嵌入微调

Model2Vec v0.4.0+ 支持在静态嵌入之上训练自定义分类模型,v0.7.0 增加了用于蒸馏的词表量化和可配置 pooling,v0.8.x 则重构了 tokenizer 和持久化行为。10这对包含专业词汇的知识库很有意义,例如医疗笔记、法律参考资料或特定领域术语,因为默认 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")

对于大多数知识库,默认的 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 模式。首次下载后,模型会缓存在本地。

维度不匹配。如果在未清空数据库的情况下切换模型,已存储向量的维度会与新的嵌入不同。indexer 会通过模型哈希检测到这一点,并触发完整重建索引。如果哈希检查失败(自定义模型缺少正确哈希),sqlite-vec 会在维度不匹配的 KNN 查询上报错。

大型知识库的内存压力。一次性嵌入 50,000 多个块可能消耗大量内存。indexer 会按每批 64 个处理,以限制峰值内存占用。如果内存仍然紧张,请减小 batch size。


使用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表,而不是存储一份重复的文本副本。这可以将存储需求减半,但也意味着在插入、更新或删除分块时,必须手动同步FTS5。

列。索引包含3列: - chunk_text:每个分块的主要内容(BM25权重:1.0) - section:H2标题文本(BM25权重:0.5) - heading_context:笔记标题、标签和元数据(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%

这些权重可以调节。如果您的知识库中有描述性强、能很好预测内容质量的标题,请提高section权重。如果标签全面且准确,请提高heading_context权重。

BM25胜出的场景

BM25擅长处理包含精确标识符的查询:

  • 函数名:_rrf_fuseembed_batchget_stale_files
  • CLI flags:--incremental--vault--model
  • 配置键:bm25_weightmax_tokensbatch_size
  • 错误消息:SQLITE_LOCKEDConnectionRefusedError
  • 特定术语:PostToolUsePreToolUseAGENTS.md

对于这些查询,BM25会立即找到精确匹配。向量搜索会返回语义相关的内容,但可能会把精确匹配排在概念性讨论之后。

BM25失效的场景

当查询使用的术语与已存储内容不同时,BM25会失效:

  • 查询:“how to handle authentication failures”→知识库中包含关于“login error recovery”和“session expiration handling”的笔记。由于关键词不同,BM25无法匹配。
  • 查询:“what is the best way to manage state”→知识库中包含关于“Redux store patterns”和“context providers”的笔记。由于“state management”是通过具体技术名称表达的,BM25会漏掉这些内容。

BM25在规模扩大后也会因关键词碰撞而失效。在一个包含15,000个文件的知识库中,搜索“configuration”会匹配数百条笔记,因为几乎每个项目笔记都会提到configuration。结果在技术上正确,但实际用途很低——排名无法判断哪条“configuration”笔记与当前查询相关。

FTS5分词器

FTS5默认使用unicode61分词器,可处理ASCII和Unicode文本。对于包含大量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个字符切分一次,可以支持子字符串匹配,但代价是索引体积增大(约为原来的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 扩展将向量 KNN(K-Nearest Neighbors)搜索引入 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 < ? 约束,从而可以在大型结果集上进行基于游标的分页,而无需重新扫描前面的页面。14 后续的 v0.1.8 和 v0.1.9 稳定版本主要是打包更新和 DELETE bug 修复版本,并未引入新的查询模型,因此 v0.1.7 仍然是此分页模式的功能边界。23

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 中切片的模式,降低了在大型知识库中进行探索式查询时的内存占用。

vec0 表中的 DELETE 支持

sqlite-vec v0.1.7 为 vec0 虚拟表添加了原生 DELETE 支持,v0.1.9 修复了一个涉及长度超过 12 个字符的元数据文本列的 DELETE 错误路径。1423 以前,移除向量需要删除并重建整张表。现在,索引器的文件移除路径可以直接删除向量:

# 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])

当笔记被删除或移动时,这简化了增量重建索引。索引器不再需要维护影子“active IDs”表,也不需要批量重建。

Vector Search 适用的场景

当概念比具体词语更重要时,Vector Search 表现尤为出色:

  • 查询:”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”的笔记(同一概念通过实现细节表达)

Vector Search 失效的场景

Vector Search 难以处理精确标识符:

  • 查询:_rrf_fuse → 返回关于“fusion algorithms”和“rank merging”的笔记,但实际函数定义的排序可能低于概念讨论
  • 查询:PostToolUse → 返回关于“tool lifecycle hooks”和“post-execution handlers”的笔记,而不是具体的 hook 名称

Vector Search 也不擅长处理结构化数据。JSON 配置文件、YAML 块和代码片段生成的 embeddings 更容易捕捉结构模式,而不是语义含义。包含 "review": true 的 JSON 文件,与关于代码 review 的散文式讨论相比,会生成不同的 embedding。

平稳降级

如果 sqlite-vec 加载失败(缺少扩展、不兼容的平台、损坏的库),retriever 会回退到仅使用 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

retriever 会在尝试向量查询之前检查 vec_available。禁用时,所有搜索都只使用 BM25,并跳过 RRF 融合步骤。


Reciprocal Rank Fusion(RRF)

RRF合并两个排序列表时,不需要进行分数校准。本节介绍该算法、一个查询追踪示例、k参数调优,以及为什么选择RRF而非其他方案。如需使用支持可编辑排名、场景预设和可视化架构探索器的交互式计算器,请参阅hybrid检索器深度解析

算法

RRF仅根据每个文档在各列表中的排名位置为其分配分数:

score(d) = Σ (weight_i / (k + rank_i))

其中: - k是平滑常量(按照Cormack等人3的做法,取60) - rank_i是文档在结果列表i中的从1开始计数的排名 - weight_i是可选的按列表设置的乘数(默认1.0)

在多个列表中排名靠前的文档会获得更高的融合分数。只出现在一个列表中的文档,则只从该单一来源获得分数。

为什么选择RRF而非其他方案

加权线性组合需要将BM25分数与cosine distances进行校准。BM25分数没有上限,并且会随语料库规模变化。Cosine distances则限定在[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 Fused Score
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只获得单一来源分数,因此会排在双列表命中的结果之后。真正的分歧解决逻辑胜出,是因为两种方法都找到了它:BM25通过关键词找到,向量搜索通过语义找到。

如需查看包含逐排名RRF计算过程的完整分步追踪,并尝试不同的k值,请使用交互式RRF计算器

实现

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分数时(很少见,但如果它们在一个列表中排名相同,且都未出现在另一个列表中,就可能发生),按以下规则打破平局:

  1. 优先选择同时出现在两个列表中的chunk,而不是只出现在一个列表中的chunk
  2. 对于同时出现在两个列表中的chunk,优先选择合计排名更低的那个
  3. 对于只出现在一个列表中的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,基于Apple M3 Pro硬件上包含49,746个chunk的数据库测得。

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工具可用范围的上下文。估算方式采用每个token约4个字符(对英文散文而言是合理近似)。结果会按贪心策略截断:按照排名顺序逐条添加结果,直到预算耗尽。

这是一种保守策略。更复杂的方法会考虑每条结果的质量分数,并优先选择更短、质量更高的结果,而不是更长、质量更低的结果。贪心方法更简单,实践中效果也很好,因为RRF排名已经按相关性对结果排序。

数据库Schema(完整)

-- 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

检索器会在初始化时检查能力,并相应调整查询策略。缺失某个组件会降低质量,但不会导致错误。唯一的硬性失败是数据库文件缺失。

生产环境统计

基于一个包含16,894个文件、49,746个chunk、83 MB SQLite数据库的知识库测得,硬件为Apple M3 Pro:

指标
文件总数 16,894
chunk总数 49,746
数据库大小 83 MB
BM25查询延迟(p50) 12ms
向量查询延迟(p50) 8ms
RRF融合延迟 3ms
端到端搜索延迟(p50) 23ms
完整重建索引时间 约4分钟
增量重建索引时间 <10秒
embedding模型 potion-base-8M (256-dim)
BM25候选池 30
向量候选池 30
默认结果限制 10
默认token预算 4,000 tokens

内容哈希与变更检测

索引器需要知道自上次索引运行以来哪些文件发生了变化。本节介绍变更检测机制和哈希策略。

文件修改时间比较

索引器会为chunks表中的每个chunk存储mtime_ns(文件修改时间,单位为纳秒)。在增量运行时,索引器会:

  1. 扫描知识库中允许文件夹下的所有.md文件
  2. 从文件系统读取每个文件的mtime_ns
  3. 与数据库中存储的mtime_ns进行比较
  4. 识别3类情况:
  5. 新文件:路径存在于文件系统中,但不存在于数据库中
  6. 已变更文件:路径同时存在于两者中,但mtime_ns不同
  7. 已删除文件:路径存在于数据库中,但不存在于文件系统中
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比较偶尔会触发对未变更文件的不必要重建索引(误报),但不会漏掉实际变更。误报的代价是每次运行多出少量embedding调用。速度差异(100ms对3秒)使mtime成为一个会在每次AI交互中运行的系统的务实选择。

处理删除

当某个文件从知识库中删除时,索引器会从数据库中移除该文件的所有chunk:

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],
    )

自sqlite-vec v0.1.7起,DELETE FROM chunk_vecs语句可原生工作;v0.1.9还修复了针对包含较长元数据文本列的vec0表执行DELETE操作的bug。1423更早版本需要使用变通方案(删除并重建虚拟表,或维护外部“活动ID”集合)。如果运行的是0.1.9之前的版本,请先升级,再依赖元数据密集型Schema中的直接删除。

FTS5内容同步表需要通过INSERT INTO chunks_fts(chunks_fts, rowid, ...) VALUES('delete', ?, ...)为每个被移除的行显式执行删除。索引器会在文件移除流程中处理此操作。


增量与完整重新索引

索引器支持两种模式:增量(快速,日常使用)和完整(较慢,偶尔使用)。本节说明何时使用各模式、幂等性保证,以及损坏恢复。

增量重新索引

使用场景:编辑笔记后的日常索引。默认模式。

执行内容: 1. 扫描Obsidian库中的文件变更(mtime比较) 2. 删除已删除文件的chunks 3. 对已变更文件重新chunk并重新embedding 4. 为新文件插入新chunks 5. 同步FTS5索引

典型耗时:对于含16,000个文件的Obsidian库,索引一天的编辑通常少于10秒。

python index_vault.py --incremental

完整重新索引

使用场景: - 更换embedding模型后(检测到模型哈希不匹配) - schema迁移后(新增列、索引变更) - 数据库损坏后(完整性检查失败) - 增量索引产生异常结果时

执行内容: 1. 删除所有现有数据(chunks、vectors、FTS5条目) 2. 扫描整个Obsidian库 3. 对所有文件进行chunk 4. 对所有chunks进行embedding 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

事实来源始终是Obsidian库中的文件,而不是数据库。数据库是可随时重建的派生产物。这是一项关键设计属性:无需备份数据库。

--incremental标志

当索引器使用--incremental运行时:

  1. 模型哈希检查。将存储的模型哈希与当前模型进行比较。如果不同,则自动切换到完整重新索引模式,并警告用户。
  2. 文件扫描。遍历允许的文件夹,收集文件路径和mtimes。
  3. 变更检测。与已存储的数据进行比较。
  4. 批处理。以64个文件为一批,对已变更文件重新chunk并重新embedding。
  5. 进度报告。打印已处理文件数量和耗时。
  6. 优雅关闭。处理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、embedding并存储到数据库中。搜索“authentication”会返回包含真实机密的chunk。更糟的是,如果retriever通过MCP将结果提供给AI工具,这些机密会出现在AI的上下文窗口中,并可能进入工具日志。

基于模式的过滤

凭据过滤器会在存储前处理每个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

关键设计选择:

  1. 先过滤,再embedding。被清理后的文本才会用于embedding。向量表示永远不会编码凭据模式。查询“API key”会返回讨论API密钥管理的笔记,而不是包含真实密钥的笔记。

  2. 替换,而非删除。[REDACTED:pattern-name]token会保留周围文本的语义上下文。embedding会捕捉到“这里曾有类似凭据的内容”,但不会编码凭据本身。

  3. 记录模式,不记录值。过滤器会记录匹配到哪些模式(例如,“Scrubbed 2 credential(s) from oauth-debug.md [jwt, bearer-token]”),但绝不会记录凭据值。

基于路径的排除

.indexignore文件提供按路径进行的粗粒度排除。凭据过滤器则在已索引文件内部提供细粒度清理。两者缺一不可:

  • .indexignore用于已知包含敏感内容的整个文件夹(健康笔记、财务记录、职业文档)
  • 凭据过滤器用于清理意外嵌入在其他可索引内容中的机密

数据分类

对于包含多样化内容的Obsidian库,可以考虑按敏感级别对笔记分类:

级别 示例 是否索引? 是否过滤?
公开 博客草稿、技术笔记
内部 项目计划、架构决策
敏感 薪酬数据、健康记录 否(.indexignore) N/A
受限 凭据、私钥 否(.indexignore) N/A

MCP 服务器架构

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服务运行。适用于远程访问、多客户端设置,或知识库位于共享服务器上的团队配置。

{
  "mcpServers": {
    "obsidian": {
      "url": "http://localhost:3333/mcp"
    }
  }
}

建议:个人知识库使用STDIO。它更简单、更安全(无网络暴露),并且服务器生命周期由AI工具管理。只有当多个工具或多台机器需要并发访问同一个知识库时,才使用HTTP。

MCP规范演进。2025年6月的MCP规范新增了OAuth 2.1授权、结构化工具输出(类型化返回schema)和elicitation(服务器发起的用户提示)。2025年11月版本将Streamable HTTP作为一等传输模式发布,同时提供.well-known URL发现以自动浏览服务器能力、用于声明工具是只读还是会变更状态的结构化工具注解,以及SDK层级标准化系统。79下一次规范发布(暂定2026年年中)提议加入用于长时间运行任务的异步操作、面向医疗和金融等行业的领域特定协议扩展,以及用于多代理工作流的代理间通信标准。9对于个人知识库服务器,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——便利工具。运行搜索,并将结果格式化为适合注入对话的上下文块。

{
  "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服务器应执行严格边界:

  1. 只读。服务器读取知识库和索引数据库。它不会创建、修改或删除笔记。写入操作(捕获新笔记)由单独的hook或skill处理,而不是由MCP服务器处理。

  2. 限定于知识库。服务器只读取已配置知识库路径内的文件。必须拒绝路径遍历尝试(../../etc/passwd)。

  3. 凭据过滤输出。即使数据库包含预先过滤的内容,也应在输出时应用凭据过滤,作为纵深防御措施。

  4. 限制token的响应。对所有工具响应强制执行max_tokens,防止AI工具接收过大的上下文块。

错误处理

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 集成

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_searchobsidian_read_note 等)。

Hook 集成

Hook 会在定义好的生命周期节点扩展 Claude Code 的行为。与 Obsidian 集成相关的 Hook 有两个:

PreToolUse hook——在 agent 处理工具调用之前查询 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,供 hook 和技能调用:

# 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 成为自动化情报流水线的下游使用方。

船长日志。/captains-log 命令会汇总所有代码仓库的每日 git 活动,向 vault 写入结构化日志条目,并包含已做决策、获得的认识和未结事项:

/captains-log

该命令会从 GitHub 拉取提交历史,按代码仓库分组,并格式化为叙事型日志条目。随着时间推移,每日日志会形成一份可搜索的记录,说明交付了什么以及原因。

Obsidian 捕获。/obsidian-capture 命令会从当前 Claude Code 会话中提取一条洞察,并带着正确元数据直接写入 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。超过这个范围会占用 agent 的工作记忆。
  • 包含来源归属。务必包含文件路径和章节标题,使 agent 能够引用来源。
  • 截断 chunk 文本。较长的 chunk 应使用 ... 截断,而不是完全省略。前 300-500 个字符通常包含关键信息。
  • 不要在每次工具调用时都注入。PreToolUse hook 应根据被调用的工具有选择地注入上下文。读取操作不需要 vault 上下文;Write 和 Edit 操作则会从中受益。

Codex CLI 集成

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
Hook ~/.claude/hooks/ 不支持
技能 ~/.claude/skills/ 不支持
指令文件 CLAUDE.md AGENTS.md
审批模式 --dangerously-skip-permissions suggest / auto-edit / full-auto

关键区别:Codex CLI 不支持 Hook。因此,自动上下文注入模式(PreToolUse hook)不可用。请改为在 AGENTS.md 中加入明确指令,要求 agent 在开始工作前搜索 vault。

Cursor和其他工具

支持MCP的Cursor和其他AI工具可以连接到同一个Obsidian MCP服务器。本节介绍常见工具的配置方法。

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文件可以包含使用知识库的说明:

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 设置UI
Claudian(Obsidian插件) N/A(嵌入式) Claude Code CLI Obsidian插件设置
Agent Client(Obsidian插件) N/A(嵌入式) ACP Obsidian插件设置

非MCP工具的备用方案

对于不支持MCP的工具,可以将检索器封装为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缓存

知识库中的结构化笔记可以作为可复用的上下文块,在多次AI交互中减少token使用量。本节介绍缓存键设计和token预算管理。

模式

与其在每次交互时都搜索上下文,不如从结构良好的知识库笔记中预先构建上下文块并缓存起来:

# 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
    },
}

缓存失效

缓存失效基于两个信号:

  1. TTL过期。 每个上下文块都有存活时间。TTL过期后,会通过重新查询知识库来重建该块。
  2. 知识库变更检测。 当索引器检测到参与构建某个缓存上下文块的文件发生变化时,该块会立即失效。

Token预算管理

会话开始时有一个总上下文预算。缓存块会消耗其中一部分:

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)

缓存块会在会话启动时加载。动态搜索结果则按每次查询填充剩余预算。这种hybrid方式既为agent提供常用上下文基线,又保留了处理特定查询的预算。

Token使用量前后对比

不使用缓存: 每个相关查询都会触发一次知识库搜索,返回1,500-2,000个token的上下文。一个会话中执行10次查询时,agent会消耗15,000-20,000个token的知识库上下文。

使用缓存: 3个预构建上下文块总共消耗4,500个token。额外搜索会为每个唯一查询增加1,500-2,000个token。若10次查询中有6次由缓存块覆盖,agent会消耗4,500 + (4 * 1,500) = 10,500个token,约为未缓存用量的一半。


用于上下文压缩的PostToolUse Hooks

工具输出可能很冗长:堆栈跟踪、文件列表、测试结果。PostToolUse hook可以在这些输出占用上下文窗口空间之前先进行压缩。

问题

运行测试的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个token,但真正的信号只有2行: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关键字 统计通过/失败数量,仅显示失败项
文件列表 命令中的lsfind 截断为前20项+总数
堆栈跟踪 Traceback关键字 保留首帧和末帧+错误消息
Git状态 modified: / new file: 按状态汇总数量
构建输出 warning: / error: 去除信息行,保留警告/错误

信号摄取与分流流水线

摄取层决定哪些内容进入库。若不加筛选,库会不断积累噪声。本节介绍将信号路由到领域文件夹的评分流水线。

来源

信号来自多个渠道:

  • RSS订阅源:技术博客、安全公告、发布说明
  • 通过Web Clipper保存的书签:官方Obsidian Web Clipper扩展(Chrome、Firefox、Safari)是浏览器端捕获中保真度最高的摄取路径。2026年4月发布周期使其对AI工作流明显更有价值:22
    • 1.4.0(4月9日):交互式YouTube字幕UI——固定视频、浏览字幕、自动滚动并高亮当前位置。此外,“Open in Reader”默认设置可将一键捕获直接发送到Reader模式。
    • 1.5.0–1.5.1(4月15日):高亮查看器——在整个库中浏览和搜索已捕获的高亮内容。进入Reader时增加淡入过渡。YouTube播放/暂停更流畅。1.5.1修复了webpack编译回归问题。
    • 1.6.0–1.6.2(4月21–23日):重做Highlighter用户体验并支持移动端。Defuddle 0.18为LinkedIn、Threads、Bluesky、Discourse和Medium新增了特定来源提取器。1.6.2修复了Safari嵌入模式下的剪贴板回归问题。 按来源域名配置模板,让YouTube字幕、GitHub README和长文分别落入命名合理的笔记,并带有下方评分流水线所需的正确frontmatter。
  • 新闻简报:来自电子邮件新闻简报的关键摘录
  • 手动捕获:阅读、对话或研究期间写下的笔记
  • 工具输出:通过hooks捕获的重要AI工具输出
  • iOS Share Extension:Obsidian的iOS应用(2026年初更新)包含Share Extension,可将Safari、社交网络和其他应用中的内容直接保存到库中,无需打开Obsidian。19这形成了一条低摩擦的移动端摄取路径——从Safari分享一篇文章,它就会作为库笔记到达,准备接受评分。
  • Obsidian CLI:Shell脚本和hooks可以通过obsidian file create创建笔记,或通过obsidian file append追加到现有笔记,从而在桌面端启用自动化摄取流水线。

评分维度

每个信号按4个维度评分(每项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

Knowledge Graph模式

Obsidian的wiki-link图谱会编码笔记之间的关系。本节介绍链接语义、用于上下文扩展的图遍历,以及会降低图谱质量的反模式。

Backlink语义

每个wiki-link都会在图谱中创建一条有向边。Obsidian会同时跟踪正向链接和backlink:

  • 正向链接:笔记A包含[[Note B]]→A链接到B
  • Backlink:笔记B显示笔记A引用了它

图谱会根据上下文编码不同类型的关系:

链接模式 语义 示例
行内链接 “与之相关” “请参阅[[OAuth Token Rotation]]了解详情”
标题链接 “包含子主题” “## Related\n- [[Token Rotation]]\n- [[Session Management]]”
类标签链接 “被归类为” “[[type/reference]]”
MOC链接 “属于其中一部分” 列出相关笔记的Maps 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通过两种方式改善检索:

  1. 直接匹配。搜索“authentication overview”会匹配MOC本身,为agent提供一份经过整理的相关笔记列表。
  2. 上下文扩展。找到某条具体笔记后,检索器可以检查该笔记是否出现在任何MOC中,并在结果中包含该MOC的结构,让agent获得更大主题范围的地图。

用于上下文扩展的图遍历

检索器的一项未来增强:找到排名靠前的结果后,通过跟随链接扩展上下文:

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)

这在当前检索器中尚未实现,但代表了图结构的自然扩展方向。

反模式

孤立集群。一组笔记彼此链接,但与库中的其他部分没有连接。Obsidian中的图谱面板会将它们显示为断开的孤岛。孤立集群表示缺少MOCs或缺少跨领域链接。

标签蔓延。标签使用不一致,或创建过多细粒度标签。一个包含5,000条笔记却有500个唯一标签的库,平均每10个标签才对应1条笔记——这些标签对筛选并无帮助。建议合并为20到50个高层级标签,并让它们映射到您的领域文件夹。

链接很多、内容很少的笔记。这类笔记完全由wiki-link组成,没有正文。这些笔记的索引效果很差,因为chunker没有文本可用于embeddings。至少添加一段上下文,说明这些被链接的笔记为什么相关。

凡事都做双向链接。并非每一次引用都需要成为wiki-link。顺带提到“OAuth”并不需要[[OAuth 2.0 Overview]]。请将wiki-link保留给有意设计、可导航的关系,也就是点击链接能提供有用上下文的场景。


开发者工作流方案

将知识库检索与日常开发任务结合起来的实用工作流。

早晨加载上下文

每天开始时,先加载相关上下文:

Search my vault for notes about [current project] updated in the last week

检索器会返回与当前项目相关的近期笔记,帮助您快速回顾上次停下的位置。这比重新阅读昨天的提交信息更有效。

编码过程中的研究捕获

实现功能时,可以在不离开编辑器的情况下记录洞察:

/capture "FastAPI dependency injection with async generators requires yield,
not return. The generator is the dependency lifecycle."
  --domain programming
  --tags fastapi,dependency-injection

捕获的洞察会立即编入索引,并可供以后检索。数月下来,这些微记录会形成一套与具体实现密切相关的知识语料。

项目启动

开始新项目或新功能时:

  1. 搜索知识库:“我对[技术/模式]了解什么?”
  2. 查看前5条结果,了解过去的决策和注意事项
  3. 检查该领域是否已有 MOC;如果没有,就创建一个
  4. 搜索失败模式:“[技术]的问题”

使用知识库搜索进行调试

遇到错误或异常行为时:

Search my vault for [error message or symptom]

过去的调试笔记通常包含根因和修复方法。对于跨项目反复出现的问题尤其有价值——知识库会记住您遗忘的内容。

代码审查准备

审查 PR 之前:

Search my vault for patterns and conventions about [module being changed]

知识库会返回与待审查代码相关的既往决策、架构约束和编码标准。审查依据的是机构知识,而不只是 diff。


性能调优

本节介绍针对不同知识库规模和使用模式的优化策略。

索引大小管理

知识库大小 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,以加快 embedding 速度 - 使用 WAL 模式(默认)支持并发访问 - 在非高峰时段运行完整重建索引

查询优化

WAL 模式。 SQLite 的 Write-Ahead Logging 模式允许索引器写入时并发读取:

db.execute("PRAGMA journal_mode=WAL")

当 MCP 服务器在索引器运行增量更新时仍需处理查询,这一点至关重要。

连接池。 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
Vector 查询 1ms 3ms 8ms 20ms
RRF 融合 <1ms <1ms 3ms 5ms
完整搜索 3ms 8ms 23ms 50ms

所有基准测试都包括数据库访问、查询执行和结果格式化。MCP STDIO 通信的网络延迟会额外增加1-2ms。


故障排除

索引漂移

症状: 搜索返回过期结果,或漏掉最近添加的笔记。

原因: 添加笔记后未运行增量索引器,或文件的 mtime 未更新(例如从另一台机器同步而来,并保留了时间戳)。

修复: 运行完整重建索引:python index_vault.py --full

更换 Embedding 模型

症状: 更换 embedding 模型后,vector 搜索返回毫无意义的结果。

原因: 旧向量(来自之前的模型)正在与新的查询向量进行比较。维度或向量空间语义并不兼容。

修复: 索引器应检测模型 hash 不匹配,并自动触发完整重建索引。如果没有自动触发,请手动清空数据库并重建索引:

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_BUSYSQLITE_LOCKED 错误。

原因: 多个进程同时写入数据库。WAL 模式允许并发读取,但只允许一个写入者。

修复: 确保只有一个进程(索引器)写入数据库。MCP 服务器和 hooks 应只读取。如果确实需要并发写入,请使用 WAL 模式并设置 busy timeout:

db.execute("PRAGMA busy_timeout=5000")  # Wait up to 5 seconds

sqlite-vec 无法加载

症状: Vector 搜索被禁用;检索器以仅 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")

大型知识库内存问题

症状: 对大型知识库(50,000篇以上笔记)进行完整重建索引时出现内存不足错误。

原因: Embedding batch size 过大,或所有文件内容被同时加载到内存中。

修复: 降低 batch size,并以增量方式处理文件:

BATCH_SIZE = 32  # Reduce from 64

同时确保索引器一次只处理一个文件(读取、chunking,并对每个文件执行 embedding 后,再移动到下一个文件),而不是将所有文件一次性加载进内存。


迁移指南

从 Apple Notes 迁移

  1. 通过“Export All”选项(macOS)导出 Apple Notes,或使用 apple-notes-liberator 等迁移工具
  2. 使用 markdownifypandoc 将 HTML 导出内容转换为 markdown
  3. 将转换后的文件移动到知识库的 00-inbox/ 文件夹
  4. 查看每篇笔记并添加 frontmatter
  5. 将笔记移动到合适的领域文件夹

从 Notion 迁移

  1. 从 Notion 导出:Settings → Export → Markdown & CSV
  2. 将导出文件解压到知识库的 00-inbox/ 文件夹
  3. 修复 Notion 特有的 markdown 产物:
  4. Notion 使用 - [ ] 表示清单——这是标准 markdown
  5. Notion 将属性表包含为 HTML——转换为 YAML frontmatter
  6. Notion 以相对路径嵌入图片——将图片复制到附件文件夹
  7. 添加标准 frontmatter(typedomaintags
  8. 将 Notion 页面链接替换为 Obsidian wiki-links

从 Google Docs 迁移

  1. 使用 Google Takeout 导出所有文档
  2. .docx 文件转换为 markdown:pandoc -f docx -t markdown input.docx -o output.md
  3. 批量转换:for f in *.docx; do pandoc -f docx -t markdown "$f" -o "${f%.docx}.md"; done
  4. 移动到知识库,添加 frontmatter,并整理到文件夹中

从普通 Markdown 迁移(未使用 Obsidian)

如果您已经有一个 markdown 文件目录:

  1. 将该目录作为 Obsidian vault 打开(Obsidian → Open Vault → Open folder)
  2. 如果该目录已纳入版本控制,请将 .obsidian/ 添加到 .gitignore
  3. 创建 frontmatter 模板并应用到现有文件
  4. 在阅读和整理时,开始使用 [[wiki-links]] 链接笔记
  5. 立即运行索引器——检索系统从第一天起即可使用

从其他检索系统迁移

如果您正在从其他 embedding/搜索系统迁移:

  1. 不要尝试迁移向量。 不同模型会产生不兼容的向量空间。请使用新模型完整重建索引。
  2. 迁移内容,而不是索引。 知识库文件才是真相来源。索引只是派生产物。
  3. 迁移后进行验证。 运行10-20个您已知答案的查询,并确认结果符合预期。

更新日志

日期 变更
2026-05-28 Obsidian 1.13.0桌面端+1.13.0移动端(Catalyst early-access)发布。桌面端:重新设计的 Settings 面板会在独立窗口中打开,内置搜索和键盘导航;Obsidian URIs现在会在触发操作前显示确认对话框;从网络驱动器加载HTML资源前新增警告;Bookmarks视图新增Search;增强了编辑器中的图片处理;File Explorer / Properties / Sync改进;大量面向开发者的API和错误修复。移动端:新的iOS Share Sheet支持配置目标位置;可从标签切换器重新排序标签;平板端支持长按手势来调整分栏和固定侧边栏大小;Bases新增菜单项,可在表格视图中调整列宽;iOS和搜索错误修复。对AI工作流的影响:Obsidian URIs的确认对话框为URI驱动的MCP/agent集成增加了一道有意设置的门禁;Bases列宽调整菜单让Bases更适合作为agent查询的vault前置索引;iOS Share Sheet可配置目标位置,使iPhone采集路径(已记录为主要输入入口)更容易快速接入Claude/Codex流水线。
2026-05-06 刷新经来源验证的时效性:Smart Connections v4.5.0将页脚连接移入Core;sqlite-vec v0.1.8/v0.1.9稳定版更新了打包方式和DELETE行为;Model2Vec v0.8.x更新了tokenizer/持久化内部机制和基准测试表;将Obsidian CLI时间线从“1.12.7引入CLI”修正为“1.12.0引入CLI,1.12.7改进安装/运行时打包”。
2026-04-27 Web Clipper 4月周期:1.4.0(交互式YouTube转录UI+默认Open in Reader)、1.5.0(Highlights viewer)、1.6.0(Highlighter UX全面改版+Defuddle 0.18针对LinkedIn/Threads/Bluesky/Discourse/Medium的来源提取器)、1.6.1+1.6.2(Reader和Safari修复)。将Web Clipper重新定位为AI工作流的主要浏览器侧输入路径,而不是顺带提到的书签工具。本窗口期内没有Obsidian桌面端、Sync或Bases版本发布。
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为维护模式(最后一次提交为2025年6月)。Dataview已趋于沉寂;Bases是新工作的后继方案。Obsidian CLI 1.12.7仍是AI助手的首选桥接方案。
2026-04-01 新增Obsidian CLI章节(面向AI工作流的v1.12命令)。新增agent插件章节(Claudian、Agent Client)。记录用于vault组织的Bases核心插件。将插件数量更新为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月)加入embedding模型对比。sqlite-vec为v0.1.7-alpha.10(CI/CD修复,无功能变更)。MCP规范和检索技术确认仍为最新。
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 初始发布

参考文献


  1. Internet Vin,“我配合 Obsidian 和 Claude Code 使用的 22 个命令”,2026年3月,x.com/internetvin/status/2026461256677245131。 

  2. Nicopreme,带有斜杠命令的“Visual Explainer”agent 技能,x.com/nicopreme/status/2023495040258261460。 

  3. Cormack, G.V., Clarke, C.L.A., and Buettcher, S. Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods. SIGIR, 2009. 介绍了 RRF,将 k=60 作为一种无需参数调优的方法,用于合并排序列表。 

  4. OpenAI Embeddings Pricing. text-embedding-3-small:每百万 tokens $0.02。估算知识库每次完整重新索引成本:约 $0.30。 

  5. van Dongen, T. et al. Model2Vec: Turn any Sentence Transformer into a Small Fast Model. arXiv, 2025. 描述了从 sentence transformers 生成静态 embeddings 的蒸馏方法。 

  6. potion-base-8M Model CardModel2Vec results。当前已发布表格显示,potion-base-8M 为 51.32 Avg (All) / 51.08 Avg (MTEB),相比 all-MiniLM-L6-v2 的 55.80 Avg (All) / 55.93 Avg (MTEB),约保留全任务分数的 92%。 

  7. Model Context Protocol Specification。用于将 AI 工具连接到数据源的 MCP 标准。 

  8. Model2Vec Potion Modelspotion-base-32Mpotion-retrieval-32M。当前 model cards 显示,potion-base-32M 为 52.83 Avg (All),potion-retrieval-32M 在检索表上为 35.06。 

  9. Update on the Next MCP Protocol Release。2025年11月发布版交付了 Streamable HTTP transport、.well-known URL discovery、structured tool annotations,以及 SDK tier standardization。下一版暂定于2026年中发布,包含异步操作、领域特定扩展和 agent-to-agent 通信。 

  10. Model2Vec Releases。v0.4.0(2025年2月):训练/微调支持。v0.5.0(2025年4月):后端重写、量化、降维。v0.7.0(2025年10月):词汇表量化、BPE/Unigram tokenizer 支持。v0.8.0/v0.8.1(2026年3月):tokenizer 与持久化重构、Python 3.9 弃用、MTEB V2 结果更新,以及 Windows 路径兼容性。 

  11. Smart Connections for Obsidian。Smart Connections v4:本地优先的 AI embeddings,初次索引后语义搜索可离线工作。 

  12. potion-multilingual-128M。Minish Lab,2025年5月。101 种语言静态 embedding 模型,表现最佳的多语言静态 embeddings。与其他 potion 模型一样,仅依赖 numpy。 

  13. MCPVault v0.11.0。2026年3月。新增 list_all_tags 工具,用于扫描 frontmatter 和 hashtags 并统计数量。改进 dotted 文件夹处理,支持 .base.canvas 文件。npm 包重命名为 @bitbonsai/mcpvault。 

  14. sqlite-vec v0.1.7 Release。2026年3月17日。稳定版:支持 vec0 虚拟表 DELETE,支持用于分页的 KNN 距离约束,改进模糊测试。DiskANN 近似最近邻索引已宣布将在未来版本发布。 

  15. Introduction to Bases。Obsidian 核心插件于 v1.9.10 引入。基于 vault 文件的类数据库视图(表格、画廊、日历、kanban boards),使用 frontmatter properties 作为字段。文件保存为 .base 格式。 

  16. Obsidian Desktop v1.12.0 ChangelogObsidian Desktop v1.12.7 Changelog。v1.12.0 引入了用于基于终端的 vault 自动化的 CLI;v1.12.7 通过独立二进制文件、TUI 和 socket 文件行为改进了安装/运行时打包。另请参阅 CLI documentation。 

  17. Claudian。Obsidian 插件,将 Claude Code 嵌入 vault,作为 AI 协作者。提供侧边栏聊天、上下文感知 prompts、视觉支持、slash commands 和权限模式。 

  18. Agent Client。Obsidian 插件,通过 Agent Client Protocol (ACP) 为 Claude Code、Codex CLI 和 Gemini CLI 提供统一界面。支持笔记提及、shell 执行和操作审批。 

  19. Obsidian iOS Changelog。2026年初更新包括 Share Extension,可将其他 apps 中的内容直接保存到 vault;还包括 Daily Note 和 Bookmark widget 修复,以及 View Note widget 刷新改进。 

  20. MarkusPfundstein/mcp-obsidian。最后一次提交为2025年6月28日。无 tagged releases。论坛讨论(2026年4月)显示,自 Obsidian 1.12.x 发布一等 CLI 后,社区已迁移到基于 CLI 的集成。本指南保留该项用于历史背景和已有设置用户,但不建议用于新部署。 

  21. Smart Connections v4.5.0 Release。2026年5月5日。Footer connections 成为 Core 功能;近期 v4 版本还包括连接列表图视图、可配置的连接面板位置、改进的 block-embedding 恢复、Substrate 跨插件状态、transformer fallback 修复,以及减少重复连接计算。 

  22. obsidianmd/obsidian-clipper releases — Web Clipper 版本-功能映射的主要来源。2026年4月周期:1.4.0(4月9日,YouTube transcript UI + Open in Reader 默认值),1.5.0(4月15日,Highlights viewer + Reader fade-in),1.5.1(4月15日,webpack 编译修复),1.6.0(4月21日,Highlighter UX + Defuddle 0.18,包含 LinkedIn/Threads/Bluesky/Discourse/Medium extractors),1.6.1(4月22日,Reader outline 修复 + highlights search),1.6.2(4月23日,Safari embedded-mode clipboard 修复)。同时列于 Mozilla Add-ons storeChrome Web Store。 

  23. sqlite-vec v0.1.8sqlite-vec v0.1.9sqlite-vec v0.1.10-alpha.3。v0.1.8 修复了 npm 打包;v0.1.9 修复了 metadata 文本列超过 12 个字符时的 DELETE bug;v0.1.10-alpha.3 添加了正确的 INSERT OR REPLACE INTO 支持,但仍为 prerelease。 

VAULT obsidian.md INDEXED