1. 为什么我要把三源笔记塞进同一个 Obsidian 库
先说清楚 llm-wiki 是什么,因为它决定了后面所有目录结构和脚本的写法。llm-wiki 是 Andrej Karpathy 提出的一个思路:把你过去积累的所有文字材料——笔记、博客、读书摘录、工作日志——当成一个语料库,让 LLM 自动从中提取概念、建立页面、编织交叉引用,最后长成一个结构化的、能持续迭代的个人 Wiki。它和传统 Wiki 最大的区别是:传统 Wiki 要你手动建页面、写摘要、加链接,枯燥且难坚持;llm-wiki 把组织成本压到接近零,你只管持续产生和收集内容,LLM 负责组织和管理。
我自己的情况是:Notion 里躺着开发知识和投资笔记,微信读书里 151 本书的划线和想法,知乎上有上千篇回答和文章。这三块内容各自成岛,搜索要开三个 App,想交叉引用基本靠脑子记。所以我的目标很明确——用 llm-wiki 的思路,把 Obsidian 作为统一的知识中枢,三源内容全部汇入同一个 vault,再通过 TaoToken 的统一 Key 和 API 通道,让 AI 反复执行摄入、更新、审计。
这篇适合谁:已经在用 Obsidian、但笔记来源分散的人;想用 Playwright 做浏览器自动化采集的人;以及想搞明白 AGENTS.md 到底该怎么写、怎么让 AI 稳定执行的人。下面给的是可复制的目录结构、AGENTS.md 配置和采集脚本,最后演示一次端到端同步与校验。
2. TaoToken 前置准备:统一 Key 与 API 通道
在动手写脚本之前,先把调用通道理顺。llm-wiki 的摄入、审计、人格蒸馏这些流程,本质都是让 LLM 读你的 Markdown 文件、提取概念、生成页面。这些操作会高频调用模型,如果每个工具各配一套 Key,管理起来很乱。我的做法是用 TaoToken 做统一入口,一个 Key 走所有流程。
TaoToken 在这里扮演的角色是统一的模型调用通道:你拿到一个 API Key,配好 Base URL,就能在脚本、Agent 工具、编辑器插件里复用同一套凭证。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api (这个不加 UTM)。注意,API 地址和官网地址是两个不同的东西,配 Base URL 的时候用后者。
具体要准备三样东西,我把它叫「三件套」,后面所有配置都围绕它:
| 配置项 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | 所有请求的根地址 |
| API Key | 在控制台生成 | 形如 sk-xxx,只显示一次 |
| Model ID | 按需选择 | 摄入用长上下文模型,审计可用轻量模型 |
拿 Key 的路径是:进控制台,找到 API Keys 页面,新建一个 Key 并立刻复制保存。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API Keys 页面是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。如果你只是想先验证模型通不通,可以直接用模型对话页面试一条请求: https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
这里有个我踩过的坑:很多人把 Base URL 写成官网首页,结果请求 404 或者返回 HTML。记住,Base URL 一定是 https://taotoken.net/api ,不带任何路径后缀。另外,Key 不要硬编码进脚本提交到 Git,用环境变量或者 .env 文件,后面配置里我会写成读取环境变量的形式。
如果你后面要长期跑编码类 Agent,比如让 AI 自动改脚本、维护 vault 结构,可以了解下 Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,遇到参数问题先查文档比瞎试快。
3. 可复制配置:目录结构、AGENTS.md 与采集脚本
这一节是全文最重的部分,我把目录结构、AGENTS.md、Playwright 脚本、微信读书同步脚本全部给全,你复制改路径就能跑。
3.1 目录结构
Obsidian vault 本质就是一个本地文件夹,llm-wiki 方法论里有一条很重要:原始素材永不可改,LLM 在原始素材之上生成结构化知识。所以我把 vault 分成 raw(原料区,只读)和 wiki(生成区,AI 可写)两层。
my-vault/ ├── AGENTS.md # 知识维护规则,AI 读这个 ├── raw/ # 原料区,永不修改 │ ├── notion-export/ # Notion 导出的 md │ ├── weread/ # 微信读书笔记 │ ├── zhihu/ # 知乎回答/文章/想法 │ └── blog/ # 博客 Markdown 源文件 ├── wiki/ # 生成区,AI 写入 │ ├── concepts/ # 概念页面 │ ├── entities/ # 实体页面 │ ├── sources/ # 来源摘要页面 │ └── index.md # 总索引 ├── personal/ # 人格蒸馏产出 │ ├── style.md # 表达风格 │ └── cognition.md # 认知特征 └── .state/ # 同步状态文件 ├── weread-synced.json └── zhihu-synced.jsonNotion 导入这块补一句:在 Notion 的「设置与成员 → 设置」里选「导出所有工作区内容」,格式选 Markdown & CSV,解压后每个页面对应一个 .md。然后在 Obsidian 社区插件市场装 Importer 插件,Cmd+P 搜「Importer: Open Importer」,选 Notion 格式导入。导入完把文件挪进 raw/notion-export/,标记为不可修改。
3.2 AGENTS.md 配置
AGENTS.md 是整个系统的规则文件,AI 每次执行前先读它。我参考 karpathy 的 llm-wiki 思路写的版本如下,重点是定义清楚摄入、更新、审计三个流程,以及硬性约束。
# AGENTS.md — 知识中枢维护规则 ## 角色 你是一个 llm-wiki 维护 Agent。你的任务是把 raw/ 下的原始素材, 转化为 wiki/ 下结构化的、带交叉引用的知识页面。 ## 硬性约束 1. raw/ 目录只读,任何情况下不得修改或删除其中文件。 2. 所有生成内容写入 wiki/ 和 personal/。 3. 每个概念页面必须包含:定义、来源引用、相关概念链接。 4. 交叉引用使用 Obsidian 的 [[wiki-link]] 格式。 5. 不确定的内容标注 `> 待核实`,不要编造。 ## 流程一:摄入(ingest) - 扫描 raw/ 下所有 .md 文件 - 提取概念、实体、来源三类信息 - 概念写入 wiki/concepts/,实体写入 wiki/entities/ - 每个来源生成一个摘要页写入 wiki/sources/ - 更新 wiki/index.md 总索引 ## 流程二:审计(audit) - 检查 wiki/ 下所有页面的链接是否有效 - 找出孤立页面(无任何入链) - 找出重复概念并合并 - 输出审计报告到 wiki/audit-report.md ## 流程三:人格蒸馏(personal) - 从 raw/zhihu/ 和 raw/blog/ 中分析表达风格 - 从 raw/weread/ 中分析认知特征和价值取向 - 产出写入 personal/style.md 和 personal/cognition.md - 注意:人格蒸馏只做归纳,不做评价 ## 模型调用 - Base URL: https://taotoken.net/api - API Key: 从环境变量 TAOTOKEN_API_KEY 读取 - 摄入用长上下文模型,审计可用轻量模型这份文件的关键在于「硬性约束」那几条。我试过不写约束,AI 会顺手改 raw 里的文件,原始数据一旦被改,后面所有引用都对不上了。
3.3 Playwright 采集知乎
知乎没有官方导出 API,我用 Playwright 做浏览器自动化。先装依赖:
pip install playwright playwright install chromium脚本核心逻辑是:首次运行打开 Chromium 让你登录,登录状态持久化到本地,后续用 --reuse 参数静默执行,增量抓取。
import asyncio import json import os from pathlib import Path from playwright.async_api import async_playwright STATE_FILE = Path(".state/zhihu-synced.json") OUT_DIR = Path("raw/zhihu") USER_DATA = Path(".state/zhihu-profile") async def sync_zhihu(reuse: bool = False): OUT_DIR.mkdir(parents=True, exist_ok=True) synced = json.loads(STATE_FILE.read_text()) if STATE_FILE.exists() else {"ids": []} async with async_playwright() as p: ctx = await p.chromium.launch_persistent_context( user_data_dir=str(USER_DATA), headless=reuse, ) page = await ctx.new_page() await page.goto("https://www.zhihu.com/people/your-id/answers") if not reuse: print("请在打开的浏览器中登录知乎,登录完成后按回车继续") input() # 滚动加载,抓取回答列表 for _ in range(20): await page.mouse.wheel(0, 3000) await asyncio.sleep(1) items = await page.query_selector_all("div.List-item") for item in items: link = await item.query_selector("a") if not link: continue href = await link.get_attribute("href") title = (await link.inner_text()).strip() item_id = href.rstrip("/").split("/")[-1] if item_id in synced["ids"]: continue # 抓正文 await page.goto(f"https://www.zhihu.com{href}") await asyncio.sleep(1.5) body = await page.query_selector("div.RichText") content = await body.inner_text() if body else "" safe = "".join(c for c in title if c.isalnum() or c in " -_")[:60] (OUT_DIR / f"{item_id}-{safe}.md").write_text( f"# {title}\n\n来源: https://www.zhihu.com{href}\n\n{content}", encoding="utf-8", ) synced["ids"].append(item_id) print(f"已同步: {title}") STATE_FILE.parent.mkdir(parents=True, exist_ok=True) STATE_FILE.write_text(json.dumps(synced, ensure_ascii=False)) await ctx.close() if __name__ == "__main__": import sys asyncio.run(sync_zhihu(reuse="--reuse" in sys.argv))首次跑python sync_zhihu.py,登录后按回车;之后跑python sync_zhihu.py --reuse就是静默增量同步。状态文件记录已抓的 id,重复运行不会重复处理。
3.4 微信读书笔记同步
微信读书开放了 Agent API Gateway,申请 API Key 后调用 /user/notebooks 拿有笔记的书籍列表,再逐本拉划线和想法。
import json import os import requests from pathlib import Path WEREAD_KEY = os.environ["WEREAD_API_KEY"] OUT_DIR = Path("raw/weread") STATE = Path(".state/weread-synced.json") def sync_weread(): OUT_DIR.mkdir(parents=True, exist_ok=True) synced = json.loads(STATE.read_text()) if STATE.exists() else {"books": []} headers = {"Authorization": f"Bearer {WEREAD_KEY}"} books = requests.get( "https://api.weread.example/user/notebooks", headers=headers ).json()["data"] for book in books: bid = book["bookId"] if bid in synced["books"]: continue detail = requests.get( f"https://api.weread.example/book/{bid}/notes", headers=headers ).json()["data"] lines = [f"# {book['title']}", f"作者: {book.get('author', '未知')}", ""] for ch in detail.get("chapters", []): lines.append(f"## {ch['title']}") for mark in ch.get("marks", []): lines.append(f"> {mark['text']}") if mark.get("note"): lines.append(f"\n笔记: {mark['note']}") lines.append("") (OUT_DIR / f"{bid}.md").write_text("\n".join(lines), encoding="utf-8") synced["books"].append(bid) print(f"已同步: {book['title']}") STATE.parent.mkdir(parents=True, exist_ok=True) STATE.write_text(json.dumps(synced, ensure_ascii=False)) if __name__ == "__main__": sync_weread()注意接口域名以微信读书官方文档为准,我这里用占位域名示意结构。输出格式是书名和作者作标题,每章划线用引用块,笔记附在对应原文下方。
3.5 统一 Key 的 settings 片段
如果你用支持 settings.json 的 Agent 工具,把三件套写进去:
{ "model": { "baseUrl": "https://taotoken.net/api", "apiKey": "${TAOTOKEN_API_KEY}", "modelId": "your-model-id" }, "knowledgeBase": { "rawDir": "raw", "wikiDir": "wiki", "agentsFile": "AGENTS.md" } }Base URL、Key、Model ID 三件套齐了,脚本和 Agent 工具就能共用同一套凭证。
4. 验证请求:跑一次端到端同步与校验
配置写完,先别急着全量摄入,用一条最小请求验证通道通不通。这一步能帮你把 401、模型名写错这类问题提前挡掉。
curl https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-id", "messages": [{"role": "user", "content": "回复 OK 两个字母"}] }'返回里能看到 choices 数组、message.content 是 OK,就说明 Key、Base URL、Model ID 三件套都对。如果返回 401,先查 Key 有没有复制全、有没有多余空格;如果返回 model not found,查 Model ID 拼写。
通道验证完,跑一次端到端同步。顺序是:先跑知乎和微信读书脚本,把 raw/ 填满;再让 Agent 读 AGENTS.md 执行一次全量摄入。
python sync_zhihu.py --reuse python sync_weread.py然后触发摄入。如果你用命令行 Agent,大致是这样:
agent run --agents-file AGENTS.md --task "执行一次全量 ingest,扫描 raw/ 下所有文件"摄入完成后校验三件事。第一,wiki/index.md 是否列出了所有来源;第二,随机点开一个概念页面,看有没有 [[wiki-link]] 交叉引用;第三,跑一次审计,看有没有孤立页面。
agent run --agents-file AGENTS.md --task "执行 audit,输出审计报告" cat wiki/audit-report.md我实测下来,151 本书的笔记加上知乎回答,第一次全量摄入大概生成几百个概念页和实体页,index.md 会变成一个可导航的总入口。审计报告里如果出现大量孤立页面,通常是概念命名不统一导致的,比如「Raft」和「Raft 算法」被当成两个概念,这时候在 AGENTS.md 里加一条命名规范,重跑摄入即可。
5. 本篇常见错排查
这一节按真实报错来,遇到对号入座。
401 Unauthorized。最常见的原因是 Key 没读到。检查环境变量:echo $TAOTOKEN_API_KEY,如果是空的,说明 .env 没 source 或者写错了变量名。另一个原因是 Key 前后有空格或换行,复制的时候带上了。还有一种是把官网地址当成了 API 地址,请求发到 https://taotoken.net/ 而不是 https://taotoken.net/api ,这种通常返回 HTML 而不是 JSON。
local proxy failed / connection refused。这类报错一般是本地网络配置问题,检查你的请求是不是走了某个本地端口。脚本里如果设了 http_proxy 环境变量,先 unset 掉再试。Playwright 启动浏览器时如果卡住,多半是 chromium 没装好,重跑playwright install chromium。
reading 'choices' of undefined。这个报错说明返回体里没有 choices 字段,通常是请求体格式不对。检查 messages 是不是数组、model 字段有没有拼错、Content-Type 是不是 application/json。还有一种情况是模型名用了不存在的 ID,返回体是错误对象,代码却直接取 choices[0],就会报这个。
OAuth / 登录态失效。知乎脚本用 --reuse 跑的时候如果抓不到内容,多半是登录态过期了。删掉 .state/zhihu-profile 目录,重新跑一次不带 --reuse 的登录流程。微信读书的 API Key 如果失效,会返回 401,去控制台重新生成。
AGENTS.md 没生效。AI 还是改了 raw/ 里的文件,说明约束没被读到。检查 AGENTS.md 是不是在 vault 根目录、文件名大小写是否一致、Agent 启动时有没有指定 --agents-file。有些工具默认读的是项目根目录的 AGENTS.md,路径不对就不生效。
摄入结果重复。同一个概念生成了多个页面,通常是每次摄入都从零开始。解决办法是在 AGENTS.md 里加一条:摄入前先读 wiki/index.md,已存在的概念做增量更新而不是新建。
6. 后续怎么用:从知识库到人格蒸馏
内容层跑通之后,我做了一件更有意思的事:拆出一个和 wiki 类似的 personal 流程,同样有 ingest、lint,区别是 wiki 的重点是知识,personal 的重点是我这个人。
这两者共享同一套 raw 素材,但目标和产出完全不同。知识库回答「我知道什么」——从笔记和读书摘录里提取客观知识,生成概念页、实体页、来源摘要页,建立密集交叉引用。人格蒸馏回答「我是谁」——从创作和阅读里反向推导认知模式、表达风格和价值取向。比如分析技术博客能归纳出「论点先行、案例驱动」的表达风格,分析知乎回答能发现「第一性原理还原」这类反复出现的认知特征。产出不是知识条目,而是一张认知图谱。
流程上两者高度同构:摄入 → 查询 → Lint → 审计。一个向外看,结构化你拥有的知识;一个向内看,建模你作为个体的认知特征。这种一体两面的设计,是我觉得整套系统最有意思的地方。
如果你要长期跑这类 Agent 任务,比如让 AI 定期自动摄入新笔记、维护 vault 结构,可以看下 Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。接入细节查文档: https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。想先验证模型效果,用模型对话页面: https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。Key 在 API Keys 页面生成: https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
最后给一个实用技巧:raw/ 目录建议用 Git 做版本管理,每次同步脚本跑完 commit 一次。这样即使 AI 摄入出错,你也能回滚到上一个干净状态。原始素材永不可改这条原则,配合 Git,基本就万无一失了。