1. 为什么需要一个“自生长”的知识库

传统笔记工具(Notion、语雀、Evernote)的问题在于:你记什么,它就只有什么。所有知识都需要人工录入、人工归类、人工维护链接。当笔记数量突破几千条后,检索靠记忆、维护靠毅力,知识库最终沦为“数字仓库”——存得多、用得上。

“自生长”知识库的核心思路是把知识管理从人工劳动升级为系统能力:

  • 自动采集:网页、PDF、音视频转写、RSS 自动落入知识库;
  • 自动理解:AI 提取核心概念、生成摘要、打标签、建立链接;
  • 自动生长:当你不断写入,系统自动发现“我应该补充什么”“哪些概念正在形成新的主题”;
  • 自动检索:用向量语义检索取代关键词匹配。

本教程用三件套实现:

工具角色
Obsidian本地 Markdown 知识库,数据完全归你所有
WorkBuddy(或 Codex CLI)AI 智能体,负责解析、总结、结构化、生成新笔记
Obsidian 社区插件 + Python 脚本采集、调度、向量化、自动链接

最终效果:你丢给系统一篇文章或随手写下一段灵感,它自动变成结构化、可检索、可生长的新知识。

本文默认你使用 macOS 或 Linux。Windows 用户命令以 WSL2 为准,我会标注差异。

2. OpenCode / Codex:AI 编码智能体的差异与选择

这里的 “Codex” 指 OpenAI 的 Codex 系列(CLI / API),而 WorkBuddy 是类似的终端 AI 编码与自动化助手。它们都能:

  • 读取本地文件、执行 shell 命令;
  • 根据自然语言指令生成并修改文件;
  • 通过 MCP(Model Context Protocol)或自定义工具连接外部服务。

本教程把它当作“知识库大脑”,负责执行我们写好的各类处理函数,比如:

  • 输入一篇文章 → 输出结构化 Markdown;
  • 输入一批笔记 → 生成 MOC(Map of Content)索引;
  • 检索失败时给出联想建议。

选择建议:

  • Codex CLI(@openai/codex):适合有 OpenAI API Key,且乐于把 CLI 接入脚本流水线的场景;
  • WorkBuddy:适合需要更丰富内置 UI、快捷键和更贴合的本地工作流场景。

无论选哪个,核心原则一致:AI 只负责“认知”工作,文件落盘、调度、版本管理由脚本负责,保证知识库可复现、可回滚。

下面先以 Codex 为例做完整闭环,WorkBuddy 的适配方法在「8. 与 WorkBuddy 结合」中说明。

3. 环境准备:从零搭建基础工作台

3.1 安装 Obsidian 与基础配置

  1. 从 obsidian.md 下载安装;
  2. 创建本地 vault,目录建议为 ~/knowledge-vault;
  3. 建好以下目录结构(后续脚本依赖它):
mkdir -p ~/knowledge-vault/{00-inbox,10-notes,20-mocs,30-templates,40-archive,90-attachments,_scripts}

目录职责:

目录用途
00-inbox待处理素材,AI 会自动清空并归档
10-notes正式原子笔记
20-mocs主题索引(Map of Content)
30-templates笔记模板
40-archive已加工完的原始素材
90-attachments图片、PDF 附件
_scripts所有自动化脚本,不参与知识检索

3.2 安装 Codex CLI

# 需要 Node.js 18+
npm install -g @openai/codex

配置 API Key:

export OPENAI_API_KEY="sk-xxxx"
# 建议写入 ~/.zshrc 或 ~/.bashrc 持久化
echo 'export OPENAI_API_KEY="sk-xxxx"' >> ~/.zshrc
source ~/.zshrc

验证:

codex --version

3.3 安装 Python 依赖

本教程的“系统骨架”用 Python 实现,AI 智能体负责其中的认知环节。

python3 -m venv ~/knowledge-vault/_scripts/venv
source ~/knowledge-vault/_scripts/venv/bin/activate

pip install pyyaml python-frontmatter watchdog \
    openai chromadb tqdm beautifulsoup4 markdownify

依赖说明:

  • python-frontmatter:读写 Obsidian 笔记的 YAML frontmatter;
  • watchdog:监听文件变动,实现“落盘即触发”;
  • chromadb:本地向量数据库,零成本语义检索;
  • markdownify / beautifulsoup4:把网页转成干净的 Markdown。

3.4 安装 Obsidian 推荐插件

社区插件从 Obsidian 设置 → 第三方插件中搜索安装:

  • Templater:笔记模板与动态变量;
  • Dataview:像查数据库一样查笔记;
  • QuickAdd:快速捕捉与执行宏;
  • Natural Language Dates:自然语言日期,供模板使用;
  • Local REST API:让外部脚本通过 HTTP 控制 Obsidian;
  • Smart Connections(可选):内置向量检索,可替代自建 Chroma。

这些插件让手写笔记和自动流水线无缝衔接。

4. 设计知识库的笔记规范

自生长的前提是格式统一。这里定义一条“原子笔记”规范,所有 AI 输出都遵守它。

4.1 frontmatter 字段

每篇笔记头部:

---
uuid: 20261022-194500-8f3a
type: note
tags: [knowledge, ai]
source:
  type: webpage
  url: https://example.com/article
  captured: 2026-10-22
aliases: []
related: []
created: 2026-10-22T19:45:00+08:00
---

# 笔记标题

正文内容...

字段含义:

  • uuid:全局唯一标识,由脚本生成;
  • type:note(原子笔记)/ moc(索引)/ snippet(代码片段)/ reflection(思考);
  • tags:标签列表;
  • source:来源元数据,便于溯源;
  • related:关联笔记 uuid 列表,由 AI 建议、脚本维护。

4.2 创建笔记模板

把以下内容保存为 ~/knowledge-vault/30-templates/note-template.md:

---
uuid: <% tp.user.uuid() %>
type: note
tags: []
source:
  type: manual
  url: ""
  captured: <% tp.file.creation_date("YYYY-MM-DD") %>
aliases: []
related: []
created: <% tp.file.creation_date("YYYY-MM-DDTHH:mm:ssZ") %>
---

# <% tp.file.title %>

## 核心要点

- 

## 详情

## 关联

<!-- related-notes -->

在 Templater 插件中把该文件夹设置为模板目录,并创建 User Script uuid.js:

// 30-templates/scripts/uuid.js
async function uuid(tp) {
  const crypto = await import("crypto");
  return crypto.randomUUID();
}
module.exports = uuid;

5. 核心实战:搭建工作流引擎

我们让 AI 负责认知,脚本负责编排。整个系统分三层:

网页/Markdown

PDF/图片

素材落入 00-inbox

识别文件类型

AI 解析与总结

OCR/转录后送 AI

生成原子笔记

写入 10-notes

向量入库

AI 建议关联笔记

更新 related 字段

归档原始素材

5.1 配置管理

创建 ~/knowledge-vault/_scripts/config.yaml(新增 AI 配置文件仍属你的本地可编辑内容,前端会按编辑器区块 ID 写入):

vault_root: /Users/yourname/knowledge-vault
openai_api_key_env: OPENAI_API_KEY
model: gpt-4o-mini
chroma_dir: .chroma
inbox: 00-inbox
notes: 10-notes
mocs: 20-mocs
archive: 40-archive

5.2 事件监听与素材捕获

用 watchdog 监听 00-inbox,一旦有文件出现就触发处理。创建 watcher.py:

import time
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from processor import process_inbox_file


class InboxHandler(FileSystemEventHandler):
    def on_created(self, event):
        if event.is_directory:
            return
        path = Path(event.src_path)
        # 文件可能还没写完,稍等再处理
        time.sleep(2)
        if path.exists():
            process_inbox_file(path)


if __name__ == "__main__":
    import yaml

    cfg = yaml.safe_load(Path("config.yaml").read_text())
    inbox = Path(cfg["vault_root"]) / cfg["inbox"]

    observer = Observer()
    observer.schedule(InboxHandler(), str(inbox), recursive=False)
    observer.start()
    print(f"监听中: {inbox}")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()

5.3 AI 处理入口:把素材变成原子笔记

创建 processor.py。这里用 OpenAI 官方 Python SDK,通过 codex CLI 批处理也可以,但直接用 SDK 更可控。

import hashlib
import uuid as uuid_lib
from datetime import datetime, timezone
from pathlib import Path

import frontmatter
import yaml
from openai import OpenAI


def load_cfg():
    return yaml.safe_load(Path(__file__).parent.joinpath("config.yaml").read_text())


def prompt_for_note(text: str) -> str:
    return f"""你是一名知识管理助手。请把下面的原始素材加工成一篇中文 Obsidian
原子笔记。要求:

1. 输出完整的 frontmatter(YAML),字段如下:
   uuid: 留空,由系统填充
   type: note
   tags: 3-6 个英文小写标签
   source: 用已知信息填充
   aliases: 关键别名
   related: 暂时留空
   created: 当前时间 ISO8601
2. 正文包含「核心要点」「详情」「关联」三个二级标题。
3. 使用简洁、信息密度高的表达,保留原文关键事实和数据。
4. 只输出 Markdown,不要任何额外解释。

原文如下:

{text[:12000]}
"""


def process_inbox_file(path: Path):
    cfg = load_cfg()
    vault = Path(cfg["vault_root"])
    notes_dir = vault / cfg["notes"]
    archive_dir = vault / cfg["archive"]

    raw = path.read_text(encoding="utf-8", errors="ignore")
    client = OpenAI()

    resp = client.chat.completions.create(
        model=cfg["model"],
        temperature=0.4,
        messages=[{"role": "user", "content": prompt_for_note(raw)}],
    )
    md = resp.choices[0].message.content

    # 用当前时间戳生成稳定 uuid
    new_uuid = uuid_lib.uuid4().hex
    note = frontmatter.loads(md)
    note["uuid"] = new_uuid
    note["created"] = datetime.now(timezone.utc).isoformat()

    file_stem = f"{datetime.now():%Y%m%d-%H%M%S}-{new_uuid[:8]}"
    out_path = notes_dir / f"{file_stem}.md"
    out_path.write_text(frontmatter.dumps(note), encoding="utf-8")

    # 归档原始素材
    archive_path = archive_dir / path.name
    path.rename(archive_path)

    print(f"[OK] {path.name} -> {out_path.name}")

启动监听:

cd ~/knowledge-vault/_scripts
../venv/bin/python watcher.py

现在把任意一篇文章粘成 Markdown 丢进 00-inbox,几秒后它会出现在 10-notes,且被重新组织为原子笔记。

5.4 用 Codex 做“需要推理”的增强

纯 SDK 调用适合批量总结,但有些任务需要多步推理 + 读写文件,比如“把这 10 篇笔记整理成 MOC”。这种场景交给 Codex 更合适。

先创建一个任务目录,Codex 以工作区方式操作:

mkdir -p ~/knowledge-vault/_scripts/codex-jobs
cd ~/knowledge-vault/_scripts/codex-jobs

执行一个交互式会话:

codex "读取 ../10-notes 下最近 10 篇关于 AI 的笔记,生成一篇 MOC 索引,
保存为 ../20-mocs/AI-MOC.md。要求:按主题分小节,每题下用列表链接到原笔记。"

Codex 会自主读取目录、分析内容、生成文件。你可以在会话中继续追加要求,如“再给我总结每篇的核心论点”“检查哪些笔记互相重复”。

把同样逻辑固化成非交互脚本 build_moc.sh:

#!/usr/bin/env bash
# build_moc.sh <topic> <output>
TOPIC="$1"
OUTPUT="$2"
cd ~/knowledge-vault/_scripts/codex-jobs || exit 1
codex --no-interactive \
  "读取 ../10-notes 下与“${TOPIC}”相关的笔记,生成中文 MOC 到 ../${OUTPUT}。\
   使用与这些笔记一致的 frontmatter,type 为 moc,related 列出被引用笔记的 uuid。"

运行示例:

bash build_moc.sh "向量数据库" "20-mocs/Vector-DB-MOC.md"

6. 语义检索与自动关联:让知识“生长”

没有关联的知识库是孤立的,AI 自动关联是“生长”的关键一步。

6.1 向量入库

创建 indexer.py,把 10-notes 里所有笔记向量化存入 Chroma:

from pathlib import Path

import chromadb
import frontmatter
import yaml
from openai import OpenAI


def index_notes():
    cfg = yaml.safe_load(Path("config.yaml").read_text())
    vault = Path(cfg["vault_root"])
    notes_dir = vault / cfg["notes"]

    client = OpenAI()
    chroma = chromadb.PersistentClient(path=str(vault / cfg["chroma_dir"]))
    collection = chroma.get_or_create_collection("notes")

    ids, docs, metas = [], [], []
    for md_file in notes_dir.glob("*.md"):
        post = frontmatter.loads(md_file.read_text(encoding="utf-8"))
        text = post.content.strip()
        if not text:
            continue
        emb = (
            client.embeddings.create(model="text-embedding-3-small", input=text)
            .data[0]
            .embedding
        )
        ids.append(post.get("uuid", md_file.stem))
        docs.append(text)
        metas.append(
            {
                "path": str(md_file.relative_to(vault)),
                "tags": ",".join(post.get("tags", [])),
            }
        )

    collection.upsert(ids=ids, documents=docs, embeddings=None, metadatas=metas)
    print(f"已索引 {len(ids)} 篇笔记")


if __name__ == "__main__":
    index_notes()

为演示清晰,这里一次生成 embedding 再批量入库;笔记量大时可以在 index_notes 里改成逐批 collection.add 处理。

运行:

python indexer.py

6.2 语义检索命令

创建 search.py:

import sys
from pathlib import Path

import chromadb
import yaml
from openai import OpenAI


def search(query: str, top_k: int = 5):
    cfg = yaml.safe_load(Path("config.yaml").read_text())
    vault = Path(cfg["vault_root"])
    client = OpenAI()
    chroma = chromadb.PersistentClient(path=str(vault / cfg["chroma_dir"]))
    collection = chroma.get_collection("notes")

    emb = (
        client.embeddings.create(model="text-embedding-3-small", input=query)
        .data[0]
        .embedding
    )
    result = collection.query(query_embeddings=[emb], n_results=top_k)
    for doc_id, meta, dist in zip(
        result["ids"][0], result["metadatas"][0], result["distances"][0]
    ):
        print(f"[{dist:.3f}] {meta['path']}  ({meta['tags']})")


if __name__ == "__main__":
    search(" ".join(sys.argv[1:]))

试一下:

python search.py "如何本地部署大模型并提供 API"

6.3 自动生成关联链

创建 link_suggest.py,为每篇新笔记找出最相关的 5 篇,再请 AI 判断应该建立哪些关联:

from pathlib import Path

import chromadb
import frontmatter
import yaml
from openai import OpenAI


def suggest_links(note_path: Path, top_k: int = 8):
    cfg = yaml.safe_load(Path("config.yaml").read_text())
    vault = Path(cfg["vault_root"])
    client = OpenAI()
    chroma = chromadb.PersistentClient(path=str(vault / cfg["chroma_dir"]))
    collection = chroma.get_collection("notes")

    post = frontmatter.loads(note_path.read_text(encoding="utf-8"))
    text = post.content.strip()
    emb = (
        client.embeddings.create(model="text-embedding-3-small", input=text)
        .data[0]
        .embedding
    )
    result = collection.query(query_embeddings=[emb], n_results=top_k + 1)

    candidates = []
    for doc_id, meta in zip(result["ids"][0], result["metadatas"][0]):
        if doc_id == post.get("uuid"):
            continue
        candidates.append(f"- {doc_id}: {meta['path']}")

    prompt = f"""当前笔记 uuid:{post.get('uuid')}
候选相关笔记:
{chr(10).join(candidates)}

请从中选出 3-5 篇与当前笔记真正相关的,输出它们的 uuid 列表(一行一个)。"""

    resp = client.chat.completions.create(
        model=cfg["model"],
        temperature=0,
        messages=[{"role": "user", "content": prompt}],
    )
    ids = [line.strip() for line in resp.choices[0].message.content.splitlines()]
    return ids[:5]


if __name__ == "__main__":
    import sys

    ids = suggest_links(Path(sys.argv[1]))
    print(ids)

把关联写回 frontmatter 的逻辑放在 relate.py:

import sys
from pathlib import Path

import frontmatter
from link_suggest import suggest_links

path = Path(sys.argv[1])
post = frontmatter.loads(path.read_text(encoding="utf-8"))
post["related"] = suggest_links(path)
path.write_text(frontmatter.dumps(post), encoding="utf-8")
print("related 已更新,请手动 review")

关联写入采用“半自动”策略:先写回 frontmatter,用户可以在 Obsidian 里人工确认,避免 AI 过度自信产生错误链接。

把这两步接入 processor.py 的流程末尾,就实现了“新笔记落盘 → 自动找关联”的闭环。

7. 从采集到生长:完整流水线示例

现在把所有环节串成一条“采集 → 结构化 → 关联 → 索引”的流水线。创建 pipeline.py:

import sys
from pathlib import Path

from processor import process_inbox_file
from indexer import index_notes
from relate import relate_note


def run(inbox_file: str):
    p = Path(inbox_file)
    if not p.exists():
        print("文件不存在")
        return
    # 1. AI 加工成原子笔记
    process_inbox_file(p)

    # 2. 找到刚归档前的原始文件对应生成的最新笔记
    notes = sorted(
        Path("10-notes").glob("*.md"), key=lambda x: x.stat().st_mtime, reverse=True
    )
    newest = notes[0]

    # 3. 建议关联并写回
    relate_note(newest)

    # 4. 重建索引
    index_notes()


if __name__ == "__main__":
    run(sys.argv[1])

一次性跑通:

python pipeline.py "00-inbox/我的随手笔记.md"

终端输出中的“已索引 N 篇笔记”就是整个流程成功的标志。

8. 与 WorkBuddy 结合:换一个“大脑”同样运行

WorkBuddy 的定位与 Codex 类似,只是交互方式更偏重本地工作区与任务面板。替换成本很低:

  • Step 1:把 _scripts 作为 WorkBuddy 的工作区打开;
  • Step 2:把上一节生成 MOC 的提示词复制到 WorkBuddy 的任务输入中;
  • Step 3:让 WorkBuddy 调用 python pipeline.py 这类命令,而不是要求它自己写全部 Python;
  • Step 4:在 WorkBuddy 中保存常用的“知识库任务模板”,例如:
任务:整理本周新增笔记
步骤:
1. 读取 10-notes 下本周创建的笔记;
2. 按主题聚类,每组生成一条 MOC 摘要;
3. 对每条新笔记运行 relate_note;
4. 运行 index_notes 重建索引。

关键点:AI 智能体不是要替代流水线,而是成为流水线里最灵活的一个组件。当你需要它“读一批文件、自己决定怎么做、再执行”,就用 Codex 或 WorkBuddy;当你需要高频、稳定、可复现的批处理,就用 Python 脚本 + 定时任务。

9. 让知识库“定时生长”与防退化

9.1 定时任务

用 cron 每天整理一次,并检查断链:

crontab -e

加入:

# 每天 23:30 重建索引
30 23 * * * cd ~/knowledge-vault/_scripts && ./venv/bin/python indexer.py >> logs/index.log 2>&1

# 每周日凌晨 2 点生成 MOC
0 2 * * 0 cd ~/knowledge-vault/_scripts && bash build_moc.sh "每周摘要" "20-mocs/Weekly-MOC.md" >> logs/moc.log 2>&1

9.2 去重检测

自生长系统容易“长歪”——产生大量重复笔记。创建 dedupe_check.py:

from pathlib import Path

import chromadb
import yaml
from openai import OpenAI


def check_duplicates(threshold: float = 0.90):
    cfg = yaml.safe_load(Path("config.yaml").read_text())
    vault = Path(cfg["vault_root"])
    client = OpenAI()
    chroma = chromadb.PersistentClient(path=str(vault / cfg["chroma_dir"]))
    collection = chroma.get_collection("notes")

    result = collection.get(include=["documents", "metadatas"])
    seen = []
    for i, doc in enumerate(result["documents"]):
        emb = (
            client.embeddings.create(model="text-embedding-3-small", input=doc)
            .data[0]
            .embedding
        )
        similar = collection.query(
            query_embeddings=[emb],
            n_results=3,
            where={"uuid": {"$ne": result["ids"][i]}},
        )
        for s_id, s_dist in zip(similar["ids"][0], similar["distances"][0]):
            if s_dist < (1 - threshold):
                seen.append((result["ids"][i], s_id, s_dist))
    for a, b, d in seen:
        print(f"疑似重复: {a} <-> {b} (dist={d:.3f})")


if __name__ == "__main__":
    check_duplicates()

简单起见,这里的重复判定用的是相似度距离;距离小于 1 - threshold 时视为可能重复,供人工判断,不自动删除。

9.3 数据备份与版本控制

Obsidian vault 天然适合 Git 管理。在 vault 根目录:

cd ~/knowledge-vault
git init
echo ".obsidian/workspace.json" >> .gitignore
echo ".chroma/" >> .gitignore
git add .
git commit -m "init knowledge vault"

再用 Obsidian 官方 Git 插件定时提交,或走系统 cron 提交。即使 AI 意外写坏某篇笔记,也能从历史中恢复。

10. 常见问题与调优建议

Q1:每次重建索引都要重新 embedding,太慢怎么办?
在 indexer.py 里为每篇笔记记录 mtime 元数据,只对 mtime 变化的文件重新生成 embedding。Chroma 的 collection.get 可取出已有 metadata 做增量判断。

Q2:AI 偶发生成不符合 frontmatter 的笔记。
把输出格式要求写进提示词,并在 processor.py 里用 frontmatter.loads 做校验;解析失败就重试一次,还失败则保留原文到 40-archive 并告警,不污染 10-notes。

Q3:想支持 PDF/图片。
PDF 先本地转纯文本(pdfminer.six 或命令行 pdftotext),图片用 Obsidian 插件或本地 OCR 后送入 process_inbox_file。处理完的二进制文件放 90-attachments,笔记里用 ![[附件名]] 关联。

Q4:本地隐私敏感文档怎么办?
本方案的数据只落在你自己的 vault 和本地 Chroma 里。AI 调用会发送文本,若非常敏感,建议对 processor.py 增加脱敏函数,或改用本地模型(如 Ollama)替换 OpenAI 客户端,只需要改 model 与 base_url。

Q5:WorkBuddy 和 Codex 可以同时用吗?
可以。Codex 用于批处理与流水线,WorkBuddy 用于你坐在电脑前交互式整理,二者操作同一个 vault,注意别同时写同一篇笔记即可。

11. 总结

用 Codex/WorkBuddy + Obsidian,我们搭出的不是一个“会记笔记的软件”,而是一套会自己消化、关联、补充的知识系统:

  • 素材丢进 inbox,自动变原子笔记;
  • 每篇笔记自动找到语义邻居并建立 related 关联;
  • 主题自动聚合成 MOC;
  • 定时索引、去重检测、Git 版本管理,防止“长歪”。

启动这套系统只需要三步:

  1. 建好 vault 目录与模板;
  2. 跑 watcher.py 监听收件箱;
  3. 用 codex 或 WorkBuddy 交互式地整理、提问、生成 MOC。

数据在你本地,规则由你定义,AI 只做认知工作。剩下的,就是不断投喂素材、持续校正提示词,让这个知识库越用越懂你。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐