拓冰建站拓冰建站
首页 / 资讯中心 / 正文

在Obsidian 里触发 properties,DeepSeek Harness 用 TaoToken 跑复盘

把 Obsidian 笔记 frontmatter 里的review_trigger从none改成milestoneDeepSeek Harness 上跑着的知识库插件就会自己去凑上下文、调模型、写回一份复盘草稿。这一步之所以能稳定复现关键不在 Harness 的编排能力而在模型调用有没有一个可靠的出口先在 TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian-properties-intro 拿到 Key再把插件里的 Base URL 固定成https://taotoken.net/api剩下的才是属性设计、判级规则和回写策略。不少人在 Harness Obsidian 这条链路上卡住并不是插件写不出来而是三件事没对齐Obsidian 的 properties 到底以什么形态被插件读到、Harness 里插件什么时候消耗 Token、以及模型请求打到哪个地址。第一件事决定「改属性能不能触发」第二件事决定「跑一次复盘要花多少 Token」第三件事决定「会不会 401/404」。这篇按落地顺序把它们拆开全部给到可直接复制的配置和代码骨架最后你能得到一个最小闭环改一篇笔记的属性跑出一条复盘命令并在review/目录里看到结构化输出。在整个链路里Token 的消耗方只有一个就是 Harness 内插件调用模型那一下。Obsidian 本身、文件监听、YAML 解析、写回 Markdown 都不烧 Token。所以优化思路也很清晰把大文件切碎、把无关目录挡在门外、把低风险更新的判级交给规则而不是交给模型模型只负责「归纳、找缺口、写报告」这类它真正擅长的事。1. 一条 properties 变更是怎么走到模型调用的先把链路说清楚后面排查问题时才不会乱。Obsidian 的 properties 本质上是 Markdown 文件顶部的 YAML frontmatter经过metadataCache解析后变成结构化对象。插件监听metadataCache的changed事件就能在属性被修改后拿到文件对象和解析好的属性字典。注意是「属性被修改」不是「文件内容任意变化」这两者在实现上是同一个事件源但插件里可以用字段判断把范围收窄。一条完整的触发链大概是这样用户在 Obsidian 里打开项目/项目A.md把属性review_trigger从none改成milestoneObsidian 保存文件metadataCache重新解析 frontmatter抛出changed事件Harness 内的知识库插件收到事件读取属性判断review_trigger命中约定值插件收集上下文这篇笔记的正文、关联的候选知识、方法目录里的既有规则插件向https://taotoken.net/api发一次对话请求模型返回结构化复盘结果插件解析结果判断风险等级低风险直接写入方法目录高风险落到待审批目录写回完成后把源笔记的review_status从pending改成done。第 7 步要特别小心写回本身就是一次文件修改如果插件不设防重入和忽略目录就会变成「写回触发监听、监听触发写回」的循环。后面第 4 节的骨架里会用一个busy集合加忽略目录列表把这个口子堵上。另一个容易忽略的点是属性值的类型。Obsidian 会对属性做类型推断2024-01-01这种值会被当成日期类型插件读到的可能是时间戳而不是字符串。所以触发字段建议只用纯文本枚举值比如none/scan/milestone/health不要用日期或布尔值去兼职做触发器。如果你还没拿到可用的 Key先到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian-properties-key 完成注册和创建再去 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian-properties-keys 生成一个 API Key本文统一用YOUR_API_KEY占位。Key 只在本地环境变量或客户端配置里出现不要写进 Obsidian 笔记 frontmatter那样等于把凭据同步进了知识库。2. 准备清单Key、Base URL 与三种客户端配置这一节的目标是把「模型从哪来」这件事一次性钉死。Harness 插件、Claude Code、Codex、CC Switch 只是不同入口底层都要指向同一个 Base URL。基础信息只有三条Base URLhttps://taotoken.net/apiAPI KeyYOUR_API_KEY模型 ID以控制台模型列表里实际可用的为准本文用YOUR_MODEL_ID占位2.1 Claude Codesettings.json 写法Claude Code 走的是ANTHROPIC_*系列环境变量。配置文件一般放在~/.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: YOUR_MODEL_ID } }如果你习惯用 shell 变量而不是配置文件效果等价export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_AUTH_TOKENYOUR_API_KEY export ANTHROPIC_MODELYOUR_MODEL_ID改完配置后新开一个终端会话让环境变量重新加载。验证方式是随便发起一次对话看返回里有没有鉴权错误。详细的 Claude Code 接入说明可以对照 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian-properties-doc 。2.2 Codexconfig.toml 写法Codex 完全不走ANTHROPIC_*它读的是 TOML 配置。把下面这段放进~/.codex/config.tomlmodel YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat然后确保环境里有这个 Keyexport TAOTOKEN_API_KEYYOUR_API_KEY这里的env_key是告诉 Codex 去环境变量里找哪个名字不要把 Key 明文写进 TOML。wire_api和model的具体取值请以 Codex 当前版本和 TaoToken 文档为准不同版本字段可能有差异。2.3 CC Switch三件套一次填完如果你同时用多个客户端用 CC Switch 做配置切换最省事。新建一个供应商条目填三件套字段值Base URLhttps://taotoken.net/apiAPI KeyYOUR_API_KEY模型名YOUR_MODEL_ID命名建议写清楚用途比如taotoken-kb以后一键切换即可。切换完成后回到终端重启一次客户端进程避免旧配置残留在内存里。2.4 Harness 插件侧的环境变量Harness 内的插件是独立进程逻辑它读的是运行 Harness 的那个 shell 的环境。所以先把变量导出去再启动export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_MODELYOUR_MODEL_ID这三条是后面所有代码的前置条件。少任何一条插件那边就会拿到空值表现出来通常就是 401 或者请求发到错误的地址上。3. Obsidian 侧properties 字段怎么设计才不会被误触发属性设计的目的不是好看而是让「人改属性」和「插件做动作」之间有一份明确契约。契约越清楚模型被无意义调用的次数越少。一篇项目笔记的属性建议长这样--- title: 项目A 里程碑复盘 type: project status: active review_trigger: milestone review_scope: methods review_risk: auto review_status: idle tags: - project - review --- ## 项目背景 这里是项目背景描述…… ## 目标 - 目标一 - 目标二 ---字段含义可以固定成下面这张表插件按表实现人按表填写属性作用建议取值review_trigger唯一触发器值变化即视为一次请求none/scan/milestone/healthreview_scope这次复盘扫哪些目录methods/workflow/allreview_risk是否允许自动写入auto/manualreview_status插件回写的状态人不要手改idle/pending/done/rejected有两条纪律要守住。第一review_trigger只在需要触发的那一刻改触发结束后由插件把它改回none或保持现状但把review_status置为done避免下次保存笔记时又被判定为一次新触发。第二人工维护的规则文件单独放不参与自动重写。建议在知识库根目录放一份《知识自动更新规则.md》用自然语言写清楚风险边界# 知识自动更新规则 ## 低风险可直接写入 - 补充来源项目链接 - 追加实践记录、验证案例 - 修正错别字与格式 ## 高风险必须人工审批 - 修改既有方法论的结论 - 变更工作流程步骤 - 删除或合并既有知识条目 ## 禁止自动执行 - 修改本文件自身 - 修改 review/ 目录以外的任何脚本插件在拼 prompt 时把这份规则一并带上判级就有了统一口径。规则文件是人工维护的保证长期可控模型只是执行者不是规则制定者。4. Harness 插件骨架监听、判级、调用、回写下面是一个最小可跑骨架。插件名kb-autoupdate只是示例你可以自己命名结构也可以按 Harness 的插件规范拆分。// 示例骨架Obsidian 侧知识库自动更新插件 const { Plugin, TFile, normalizePath } require(obsidian); const TRIGGER_FIELD review_trigger; const STATUS_FIELD review_status; const IGNORE_DIRS [review/, pending/, candidate/, templates/]; module.exports class KbAutoUpdate extends Plugin { async onload() { this.busy new Set(); this.registerEvent( this.app.metadataCache.on(changed, (file, data) { this.handleChange(file, data).catch((err) { console.error([kb-autoupdate] 处理失败:, err); }); }) ); } async handleChange(file, frontmatter) { const path file.path; // 1) 忽略自动写入目录避免写回再次触发监听 if (IGNORE_DIRS.some((dir) path.startsWith(dir))) return; // 2) 防重入同一文件同一时间只处理一次 if (this.busy.has(path)) return; // 3) 只有触发器有值时才继续其余属性变更一律放过 const trigger frontmatter?.[TRIGGER_FIELD]; if (!trigger || trigger none) return; this.busy.add(path); try { await this.patchStatus(file, pending); const noteText await this.app.vault.read(file); const rules await this.readRules(); const result await this.callModel(trigger, path, noteText, rules); await this.dispatch(file, frontmatter, result); await this.patchStatus(file, done); } catch (err) { console.error([kb-autoupdate] 复盘失败:, err); await this.patchStatus(file, idle); } finally { this.busy.delete(path); } } async readRules() { const ruleFile this.app.vault.getAbstractFileByPath(知识自动更新规则.md); if (ruleFile instanceof TFile) { return await this.app.vault.read(ruleFile); } return 无规则文件默认按高风险处理。; } async callModel(trigger, path, content, rules) { const base process.env.TAOTOKEN_BASE_URL || https://taotoken.net/api; const key process.env.TAOTOKEN_API_KEY; const model process.env.TAOTOKEN_MODEL; if (!key || !model) { throw new Error(缺少 TAOTOKEN_API_KEY 或 TAOTOKEN_MODEL 环境变量); } const prompt [ 触发类型${trigger}, 源文件${path}, 请按下面的规则判定风险等级并输出 JSON。, 字段要求risklow/high、summary一句话、findings数组、patch低风险时的建议内容。, 规则文件内容如下, rules, 笔记正文如下, content.slice(0, 6000) ].join(\n); const resp await fetch(${base}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${key} }, body: JSON.stringify({ model, messages: [ { role: system, content: 你是知识库复盘助手只输出合法 JSON不要解释。 }, { role: user, content: prompt } ], response_format: { type: json_object }, temperature: 0.2 }) }); if (!resp.ok) { const text await resp.text(); throw new Error(模型请求失败 ${resp.status}: ${text.slice(0, 300)}); } const data await resp.json(); const raw data?.choices?.[0]?.message?.content || {}; return JSON.parse(raw); } async dispatch(file, frontmatter, result) { const scope frontmatter?.review_scope || methods; const risk result?.risk low ? low : high; const targetDir risk low ? methods : pending; const report [ ---, source_note: [[${file.path}]], scope: ${scope}, risk: ${risk}, generated_at: ${new Date().toISOString()}, review_status: ${risk low ? applied : waiting_approval}, ---, , ## 复盘摘要, , result?.summary || 模型未返回 summary, , ## 发现, , ...(Array.isArray(result?.findings) ? result.findings.map((f) - ${f}) : [- 无]), , ## 建议变更, , result?.patch || 无 ].join(\n); await this.writeFile(${targetDir}/${file.basename}-复盘.md, report); } async writeFile(path, content) { const normalized normalizePath(path); const existing this.app.vault.getAbstractFileByPath(normalized); if (existing instanceof TFile) { await this.app.vault.modify(existing, content); } else { const folder normalized.split(/).slice(0, -1).join(/); if (folder !this.app.vault.getAbstractFileByPath(folder)) { await this.app.vault.createFolder(folder); } await this.app.vault.create(normalized, content); } } async patchStatus(file, status) { await this.app.fileManager.processFrontMatter(file, (fm) { fm[STATUS_FIELD] status; if (status done) fm[TRIGGER_FIELD] none; }); } };几个设计点值得单独说。上下文裁剪。content.slice(0, 6000)是硬截断目的是控制单次请求的 Token 消耗。真实项目里更稳的做法是按标题切块只把「目标」「结论」「变更记录」这几段送进去其余正文留着本地检索。判级交给规则不交给感觉。prompt 里明确要求输出risk字段并且把《知识自动更新规则.md》原文带进去。模型只需要做一次二分类出错概率远低于让它自由决定「该不该改」。状态回写用processFrontMatter。这个 API 会保留原有 YAML 结构不会把整份 frontmatter 重排成不可读的样子。手写正则去改 YAML遇到多行数组或注释就会出事。写回目录必须在忽略列表里。review/、pending/、candidate/三个目录一旦被监听插件就会对自己的输出再触发一次复盘Token 消耗会呈倍数上涨。5. properties 触发复盘一次可复现的完整操作前面都是准备这一节给出从改属性到看到输出的完整步骤。第一步确认环境变量已生效。echo $TAOTOKEN_BASE_URL echo $TAOTOKEN_MODEL test -n $TAOTOKEN_API_KEY echo key ok期望输出里 Base URL 是https://taotoken.net/api模型 ID 非空最后打印key ok。第二步确认插件已加载。在 Obsidian 里打开命令面板执行一次「重新加载插件」然后在插件目录里确认kb-autoupdate处于启用状态。第三步修改属性。打开项目/项目A.md把review_trigger: none改成review_trigger: milestone保存文件。第四步观察状态流转。属性review_status应该先从idle变成pending几秒到几十秒后变成done同时review_trigger被自动改回none。如果停在pending不动说明模型请求没回来去看控制台日志里的 HTTP 状态码。第五步查看输出。review/项目A-复盘.md或pending/项目A-复盘.md会出现一份结构化报告长这样--- source_note: [[项目/项目A.md]] scope: methods risk: high generated_at: 2025-01-15T09:24:11.000Z review_status: waiting_approval --- ## 复盘摘要 本次里程碑涉及工作流步骤调整判定为高风险已转入待审批。 ## 发现 - 方法目录中「需求评审」条目与本次实践流程不一致 - 缺少异常回滚的验证案例 - 候选知识里有两条未归档记录 ## 建议变更 - 更新 methods/需求评审.md 的第 3 步 - 新增 methods/异常回滚验证.md - 将 candidate/ 下两条记录合并进对应方法条目第六步验证风险分流是否正确。把同一篇笔记的review_trigger改成scan假设日志类触发如果这次只涉及「补充来源项目、追加实践记录」输出应落在methods/目录且risk: low如果涉及流程变更应落在pending/。两种结果都符合预期说明判级逻辑在正常工作。这条闭环跑通之后复盘就不再依赖你记性好而是依赖属性值的变化。你需要的只是「想起来的时候就改一下属性」。6. 两条写入路径低风险直写与高风险审批低风险和高风险走不同目录是整个系统能长期跑下去的前提。如果全自动写入一次错误的方法论变更可能污染整个知识库如果全部人工审批那自动化就没有意义。分流的意义在于把审批成本压到只占少数的高风险变更上。推荐的目录结构知识库/ ├── 知识自动更新规则.md ├── 项目/ │ └── 项目A.md ├── methods/ # 低风险直写目标 ├── workflow/ # 流程类知识通常高风险 ├── candidate/ # 候选知识等待归档 ├── pending/ # 高风险复盘报告等待人工审批 └── review/ # 例行复盘与健康检查报告审批动作本身也做成属性变更保持交互一致。在pending/里的报告把属性review_status从waiting_approval改成approved插件收到事件后把变更合入目标目录并把报告标记为merged改成rejected则原样归档不再处理。这里要提醒一句审批合入涉及改写methods/、workflow/下的既有知识条目动手前先确认知识库有版本管理比如整个 vault 用 Git 管理这样任何一次误合入都可以回退。低风险直写要控制爆炸半径。插件写入时带上source_note和generated_at任何一条自动写入的内容都能追溯到源头笔记。发现某条知识不对劲时顺着source_note就能找到是哪次复盘带进来的。7. 月度健康检查只读模式怎么写健康检查和阶段复盘是两种不同动作。复盘会改知识库健康检查只看不改产出一份报告用来回答「知识库有没有变胖、有没有孤儿条目、有没有长期没被引用的方法」。触发方式同样走属性只是这类检查通常挂在月度索引笔记上--- title: 2025-01 知识库月度检查 type: index review_trigger: health review_scope: all review_risk: manual review_status: idle ---review_risk: manual是给插件的一个硬信号无论模型返回什么风险等级都不写入methods/或workflow/报告统一落到review/目录。健康检查里可以复用的三个本地指标全部在本地算不消耗 Token# 统计候选知识条数 ls candidate/*.md 2/dev/null | wc -l # 找出 90 天未修改的方法文件 find methods -name *.md -mtime 90 # 统计没有被任何笔记引用的条目简化写法 grep -L \[\[ methods/*.md这些指标跑完后把结果拼进 prompt让模型做归纳和排序。这样 Token 花在「总结」而不是「统计」上单次成本会低很多。插件里的只读分支实现很短async dispatchReadonly(file, result) { const report [ ---, check_type: health, generated_at: ${new Date().toISOString()}, review_status: report_only, ---, , result?.summary || 无摘要 ].join(\n); await this.writeFile(review/健康检查-${Date.now()}.md, report); }写完就结束不碰methods/、workflow/、candidate/任何一个目录。月度检查的价值不在于它改了什么而在于它定期把「知识库现在长什么样」摆到你面前。8. 高频报错与排障清单这部分按报错现象归类遇到问题直接对号入座。401 / Unauthorized。三种可能环境变量没导出、Key 已失效、请求头拼错。先跑一遍环境变量检查命令再看请求头是不是标准的Authorization: Bearer YOUR_API_KEY。Harness 插件和终端不在同一个 shell 环境里是这里最常见的坑导入变量后必须重启 Harness 进程。404 / Not Found。绝大多数是 Base URL 或路径拼接问题。Base URL 固定写https://taotoken.net/api对话路径在代码里拼/v1/chat/completions不要在配置文件里手动补斜杠也不要把 Base URL 写成带/v1的形式再叠加一层/v1。403 或模型不可用。通常是YOUR_MODEL_ID填了一个当前账号不可用的模型名。以自己的控制台模型列表为准先跑通一个最基础的对话再换到复盘用的模型。改了属性但插件没反应。依次检查插件是否启用属性名大小写是否一致建议统一小写下划线属性值是不是被 Obsidian 推断成了日期或布尔类型review_trigger是不是已经等于none文件是不是落在忽略目录里。复盘跑了一半review_status卡在pending。说明请求发出去了但没拿到合法 JSON。在插件里把原始返回体打出来常见原因是模型带了 Markdown 代码围栏JSON.parse直接失败。处理办法是在解析前剥掉围栏或者用更严格的response_format约束。Token 消耗比预期高。三个方向的排查一是写回目录没进忽略列表导致自触发循环二是每次复盘都把整篇笔记原文塞进 prompt没有做分段裁剪三是低风险和高风险的判级被交给了模型自由发挥导致每次都要带上大量上下文。把忽略目录、正文截断、规则判级这三件事做掉消耗通常会明显下降。自动写入的内容格式乱了。不要用正则去改 frontmatter用processFrontMatter。手写解析在遇到多行数组、嵌套对象、注释时几乎一定会出错。多人协作时反复触发。知识库同步工具网盘、Git 自动拉取会让文件批量变更间接带动属性变化。给插件加一个基于文件修改时间和内容哈希的去重判断同一个内容哈希只处理一次。9. 落地顺序三天把一个最小闭环跑起来不要一上来就把召回、扫描、复盘、健康检查全做完那样很难定位问题出在哪一层。按下面顺序推进每一步都是可验证的。第一天把模型出口打通。到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian-properties-plan 了解接入方式在控制台创建 Key配好环境变量用命令行发一次最简对话请求确认能拿到返回。这一步不过后面所有代码都是白写。第二天把 Obsidian 侧的触发跑通。写一个最小插件只做一件事监听到review_trigger变成指定值后在review/目录写一个带时间戳的文件。不调模型纯本地。触发链路确认无误后再把模型调用接进去。第三天接判级与分流。把《知识自动更新规则.md》加进来让模型输出risk字段低风险写methods/高风险写pending/。再手动走一遍审批流程确认属性改成approved后变更能正确合入。三天之后你手里就有了一套能自跑的复盘流程改一次属性换一份结构化复盘报告。后续的召回、扫描、健康检查都是在同一个骨架上加分支成本会低得多。更进一步如果你的复盘频率上来了按量计费的模型调用会变成一笔需要考虑的开销可以看看 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian-properties-coding 里的套餐是否更合适已经跑通流程、准备把插件用到多个项目上的直接去 https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian-properties-chat 验证模型可用性再去 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentobsidian-properties-keys-final 创建一把新的 Key 专门给 Harness 用把权限和环境隔离清楚。回到最初那个动作在 Obsidian 里改一下review_trigger剩下的交给插件和模型。知识库能不能自我进化靠的不是某一次灵感而是这条链路每周都能稳定跑一遍。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门