我给自己那个本地知识库打了两年多的分。
9 月 10 日晚上,它拿到 9.3 / 10。理由很充分:23 条死链、10 篇笔记缺 frontmatter、5 个收件箱积压、一个战略主题零覆盖。
三个小时后,同一套方法、同一套权重、同一批命令,再算一遍:9.7 / 10。
这三个小时里,我没有往库里加一行新知识。只是把已经烂掉的地方挖出来扔了。
但真正的故事不在 9.3 到 9.7。真正的故事是——第一份写着"9.7"的报告里,有一处举证是不实的,而且是复核的时候才发现的。
这是"本地 AI 知识库实践"系列的第五篇。前面四篇讲的是怎么搭(从零搭建本地 AI 知识库)、怎么更聪明、怎么让数字可信(卡片索引层)、2.0 时代改了哪些东西(知识库 2.0 升级全解)。这一篇讲的是怎么证明它真的好——以及为什么"它说自己好"这句话本身不可信。
本文全部数字来自 2026-09-10 的现场实测命令,可复现。你的库不必有相同的数字,方法相同即可。
一、三个小时:完整时间线
整件事从当天晚上 22:47 开始,到 23:58 结束。
| 时间 | 动作 | 结果 |
|---|---|---|
| 22:47 | 第一次审计(六维评分模型) | 9.3 / 10,三项 P1/P2 弱点 |
| 23:00–23:18 | 弱点清零执行 | 死链 23→0、缺 frontmatter 10→0、收件箱积压 5→0 |
| 23:18–23:30 | 复审(同方法、同权重) | 9.7 / 10,九项门槛全过 |
| 23:31–23:45 | 独立复核(另一个 Agent 重跑全部指标) | 采信 9.7,但发现 1 项举证不实 + 2 项需修正 + 4 项缺口 |
| 23:40–23:58 | 缺口全量执行 | 7 项动作落地,向量块 121,951 → 122,120 |
| 收尾 | 稳态复核 | 死链 0 / frontmatter 0 / 漂移 0 / 积压 0 / 回归 21/21 |
注意中间那一行——复审和独立复核是两个不同的动作,由两个不同的角色做。
- 复审:执行方自己再算一遍,确认清零生效。
- 独立复核:换一个角色,不采信对方的任何中间值,用自己的命令把 16 项指标全部重跑一遍。
分数最终没有变。变的是分数的成分。
二、为什么"自评 9.7"这句话不能直接信
先说结论:那份报告的核心数据零失真。 16 项举证里,13 项精确复现,2 项数值一致,1 项不实。9.7 这个结论成立。
但发现不实项的过程,才是这一节的重点。
规则只有一条:不复用对方的任何数字,全部用自己的命令现场重跑。
被复核的报告声称,它在"治理与回滚证据"里列出了"交接文件 145 行的时间线"。复核时的实测是:
| |
那份交接文件实际 1,531 行,而且关于当天的工作一条记录都没有。当天有 12 个实质工作块,按项目自己定的规矩,“每次变更必须更新交接文件"是硬性要求。
所以这条证据不该出现在报告里。它被删除了,缺口由复核方当场补上。
这里有一个可复用的规律,我把它当成这次复盘最直接的收获:
能跑命令的指标容易诚实,靠"我做了"的叙述性指标容易失真。
16 项举证里,13 项是可以用一条命令跑出来的——死链数、向量块数、回归通过率、索引文件大小。这些数字全部精确复现,一项都没错。
唯一不实的那一条,恰恰是最难被随手验证的类型:一个关于"另一份文件写了什么"的叙述。
这不是道德问题,是成本结构问题。跑一条命令的成本是 3 秒,编一个数字的成本也是 3 秒——但核对的成本完全不同。所以:复核的力气应该优先压在叙述类举证上,而不是重复重跑那些本来就诚实的数字。
三、和上一版最大的区别:从「建能力」到「治质量」
如果只看功能列表,会以为 9 月这版和 8 月那版没有太大差别——还是那 2.7 万篇笔记、还是那套检索、还是那些卡片。
差别在目标上。
| 维度 | 2.0(8 月) | 3.0(9 月) |
|---|---|---|
| 目标 | 让知识库能回答业务问题 | 让知识库能证明自己是健康的 |
| 重心 | 加能力:卡片索引层、多跳检索、看板 | 保质量:审计 → 复核 → 清零 → 跨周期稳定 |
| 关键产物 | 2.3 万张标准答案卡 + 回归题库 | 六维评分模型 + 九项门槛 + 五态分离 |
| 谁来验 | 自己写、自己跑、自己看 | 生产者与复核者分离:一方清零,另一方独立复跑 |
| 判据 | 功能有没有跑通 | 数字能不能被第三方复现 |
| 典型失败 | 能回答,但答案不可信 | 可信,且不可信的地方被明确标出来 |
一句话概括这个转变:
2.0 关心"知识库能不能用”;3.0 关心"知识库说自己能用的时候,这句话可不可信"。
为什么会走到这一步? 因为库的输出开始被用在不能出错的场合——报价、对账、谈判。
在这些场景里,“数字是多少"和"数字可不可信"是两个问题,而后者更贵。一个能秒回采购金额的系统,如果偶尔捞回一份去年的快照,它带来的损失比"查不到"大得多——因为查不到你会去翻源文件,查到了错数字你不会。
这个转变也不是自发发生的。它是被反复教育出来的——见多 Agent 协同治理那篇里那些跨 Agent 的来回。当库里有五个 AI 在读写、每个都说"我做完了”,“做完"这三个字就必须被定义成可验证的状态,而不是一种说法。
于是才有了 3.0 的核心机制:五态分离。
| 状态 | 含义 |
|---|---|
source_written | 文件写进磁盘了 |
miyo_visible | 语义层能读到它了 |
rag_indexed | 进了向量索引了 |
codex_acceptance | 被独立验收了 |
complete | 真正完成了 |
这五个是不同的状态。把它们混成一个"完成”,是绝大多数"我明明做了"事故的源头。
四、具体优化了什么:7 处改动,逐条对上
这一节按"问题 → 改动 → 验证"三段式写,每条都带可复现的验证方式。
1. 评分脚本的口径分裂——同一个脚本给出两个答案
问题:质量记分卡的内部函数被单独调用时,会报告库里有约 3,200 条死链;而权威扫描器同时报告 0 条。同一台机器、同一个库、同一分钟。
根因是:内部那条计算路径没有剥离代码块。文档里写示例时用的 [[链接]],在它眼里就是真链接——而我的库里到处是这种示例。
改动:让内部函数直接取权威扫描器的结果;另设一个 deadlinks_internal 字段显式标注"这是非权威口径";并给扫描结果加一层缓存,避免同一个进程里把全库扫两遍。
验证:直接调用内部函数 →
| |
为什么这条最重要:它不是一个显示问题,是一个跨 Agent 的误判源。任何 Agent 直接调这个函数,过去都会得到"库里有三千条死链"的错误结论——然后花几个小时去"修"一个不存在的病。
2. 模板被"转义"消死链,代价是模板不能用了
问题:为了消掉模板文件里的死链,之前的做法是把 [[笔记名]] 改写成 \[\[笔记名\]\]。死链确实没了,但模板的用途就是"复制出来改"——转义之后,用户复制到新笔记里得到的是字面反斜杠。
改动:模板恢复原形;在死链扫描器里加一条豁免规则——文件名以 _template 结尾的不参与死链扫描,在扫描、修复、结构扫描三处同步生效。
验证:
- 全库 wikilink 总数 78,647 → 78,653(模板里的链接重新计入总数),而死链仍然是 0
- 修复模式空跑:修改文件 0 个
3. 一条链接被改成纯文本,静默抹掉了"待建"信号
问题:另一条死链的消法是把它改成纯文本([[X]] → 「X(笔记待建)」)。死链没了,但"这条笔记还没建"这个信号也一起没了。
处置:这一条我在复核时候改了判断。原来的结论是"应该给它建一篇占位笔记",但先查了一下这个目标到底存不存在——结果发现它早就存在,有一篇 20KB 的完整笔记,只是文件名不完全一致。
所以它根本不是"待建",是错链。
改动:把链接指向那篇真实笔记。
验证:3 条新链全部解析,死链 0。
4. 备份覆盖面小于实际改动面
问题:报告称"改动前备份 5 个文件,可回滚"。但这一轮实际触达了 11 个以上文件——另外 6 个(补 frontmatter 的、移动归位的)没有备份。
改动:补齐 5 件,并附一份带回滚命令的说明。
验证:备份件数 5 → 10。
教训:“可回滚"这句话必须用文件计数验证,不能凭感觉。 你在备份的时候脑子里想的是"我改的那几个文件”,而实际改动面永远更大。
5. 报告自己和自己矛盾
问题:报告给"链接与结构完整性"维度打了 满分 10.0;同一份报告的"保留项"章节里,却白纸黑字写着"这两维的满分与综合 10 分均不授予,因为单日修复未跨周期稳定"。
改动:链接维 10.0 → 9.9,治理维 9.6 → 9.5,综合分 9.695 → 9.675——一位小数仍然是 9.7。
这一条我单独拎出来,因为它是评分通胀的典型指纹,下一节展开。
6. 两组都对的数字被并列摆在一起
问题:报告里写"死链 23 → 0",而持久化记分卡同期的真实基线是"死链 3 → 0"。两组数都对——一个含代码块示例和非标准范围(宽口径),一个不含(权威口径)——但并列摆出来,会让读者以为它们是同一条口径链上的首尾。
改动:加基线口径注脚,明确写"两组数都对,口径不同,不可混用或相减"。
7. 单日数据不等于稳定数据
问题:所有指标在今天一天之内归零。但评分模型自己有一条:满分需要跨周期证据。
改动:不授予满分,明确标注观察窗口,等下一帧(第二天自动任务 + 周五例行审计各一帧)再评。
另有 P3:手动跑一次向量索引增量同步,让当天新增的内容当日进索引——而不是等到第二天中午的定时任务。结果:121,951 → 122,120 个向量块,回归 21/21。
五、提升在哪:6 个数字 + 3 件看不见的事
能看见的
| 指标 | 审计前 | 收尾 |
|---|---|---|
| 死链(权威口径) | 23 | 0 |
| 缺 frontmatter | 10 | 0 |
| 标签漂移 | 0 | 0 |
| 收件箱积压 | 5 | 0 |
| 未分类的待建实体 | — | 0(26 项全部分类) |
| 向量块 | 121,951 | 122,120 |
| 检索回归 | 21/21 | 21/21 |
| 健康状态 | 🟡 RED | 🟢 green |
| 综合评分 | 9.3 | 9.7 |
看不见的(其实更重要)
1. 少了一个跨 Agent 的误判源。 第 1 条改动之后,任何 Agent 拿到这台机器上的评分脚本,都只会看到真实数字。这一条的收益不在今天的分数里,在未来每一次别人复用这个脚本的时刻。
2. 模板重新可用。 转义改豁免,模板回到了"复制即用"。这是把一次治标换成了治本——同样死链归零,但一个留下了疤,一个没有。
3. 堵住了一条未来的自动回退路径。 见下一节第 5 条。如果没发现它,今天的努力每周五会被自动回滚一次,而且不报错。
六、六个反直觉发现
这一节是整篇里最值得带走的。
1. 能跑命令的指标容易诚实,靠"我做了"的叙述容易失真
16 项举证,13 项精确复现——全部都是能用一条命令跑出来的数字。
唯一不实的那一条,恰恰是唯一一条叙述性的、无法用一条命令验证的。
行动含义:复核不要把力气花在重算那些本来就诚实的数字上。把 80% 的复核时间压在那几条"我说我做了 X"上,逐条去找对应的物理文件。
2. 同一份报告自相矛盾,是"评分通胀"的指纹
数字算错,可能是手滑。
但一边给满分、一边在保留项里写"满分不授予"——这不是手滑。这说明评分是先有结论,后凑证据。
识别方法:把报告的"评分表"和"保留项/局限说明"两张表并排看。如果评分表里的高分项,在保留项里被自己否掉了,那就是膨胀信号。这个信号比任何单个数字错误都更值得警惕。
3. 修脏数据有三条路,只有一条不欠债
| 手法 | 做法 | 欠什么 |
|---|---|---|
| 转义 | [[X]] → \[\[X\]\] | 欠可用性——模板不能复制了 |
| 建占位 | 建一篇空笔记 | 欠真实内容——库里多了一篇空壳 |
| 豁免 | 扫描器跳过这个文件 | 不欠 |
但选手法之前,有一句话必须先问:
这个目标到底存不存在?
- 存在 → 是错链,改指向
- 不存在 → 是待建,建占位或登记为"延后"
- 是模板/示例 → 豁免
我在同一天里,把这三类中的前两类都判错过一次。判错的代价不是死链没消掉,而是在错误的题面上做优化——比如给一篇早就存在的笔记去建占位。
4. 口径分裂的修复,往往只修了"刚好出事的那半边"
第 1 条改动里,有一段逻辑是特判:只有在死链数等于 0 的时候,才用权威值覆盖内部值。
也就是说:死链是 0 的时候,一切正常;死链一旦大于 0,两个口径又分裂回去了。
为什么会写成这样?因为出事的时候,死链恰好是 0。修复的人(也就是我)把注意力放在了"让当前这个状态显示正确"上。
行动含义:凡看到 if X == 0: 形式的特判修复,都要追问一句——“X 大于 0 的时候怎么办?” 这类修复改一个分支,等于把陷阱推到另一个分支。
顺带一条:改这段代码时还得先数一数,那个被处理的字符串变量有几个消费者。它同时喂给 4 个不同的检测逻辑,全局处理会静默改掉另外两个跟本次无关的指标。最小侵入的做法是用局部副本。
5. 改脚本前,必须读关联自动化的原文——不能只看名字
这是本次最值钱的一次发现。
在把"模板豁免"落进脚本之前,我做了一件事:把当天所有在跑的定时任务列出来,逐个读它们的 prompt 正文。
然后在另一个任务里找到了真冲突:
有一个"每周五 16:00 全面复盘与质量审计"的任务。在它自己的正文里,写着这么一条动作:发现死链 → 运行修复脚本。
那个修复脚本对某一类死链的动作是什么?往文件里注入一行 <!-- 待建: X --> 注释。
所以:如果我只按照原方案把模板转义、不动脚本本身——每周五晚上这个任务会自动把模板重新污染一遍。
不会报错。不会告警。只会安静地、周期性地把你刚修好的样板改坏。
而它藏在另一个任务的正文里。只看任务名字(“每日死链扫描 + 修复”,听起来人畜无害)永远发现不了。
行动含义:任何一次"改脚本",前置动作不是看代码,是把全部定时任务列出来,逐条读 prompt 原文,标出哪些会写回同一个文件。
6. 最容易腐烂的不是代码,是文档里的具体路径和阈值
同一天,我把索引架构迁移成了"成对目录 + 符号链接指针"。
同一天,我自己的操作手册里的回滚步骤,还指向旧路径。
旧路径停在当天 12:15 的状态,而生产已经跑到 23:49。照着那份手册回滚,会静默退回到几个小时前——而且看起来完全成功。
架构变了,回头扫一遍所有引用它的文档——这一步没有任何工具会提醒你。代码有类型检查、有测试、有报错;文档没有。
行动含义:改架构时,同步的清单里除了代码,必须包含"grep 一遍所有文档里对旧路径/旧阈值的引用"。
七、如何一次性达成这样的结果
先说清楚:这一节不是"我这里是怎么做的"的复述。9 点到 10 点,我是踩了坑才做对的。下面是事后整理出来的、可以一次做对的路径。
三个前置条件(缺一个都做不成)
1. 一份写下来的评分模型。
维度、权重、阈值、满分条件,白纸黑字。没有它,“9.7"就只是一个感觉,而且是一个会和昨天、和别人的数字混着用的感觉。
2. 五态分离。
写入了 / 可见了 / 进索引了 / 被验收了 / 完成了——五个不同的状态。混成一个"完成”,就是"我明明做了"事故的母体。
3. 生产者与复核者分离。
清零的人和复核的人不能是同一个。这不是信任问题——是因为一个人对自己刚做完的事情,判断力会系统性下降。你会本能地重跑那些本来就会过的检查,然后跳过那些可能不通过的。
四条硬规则
| 规则 | 具体动作 | 反面 |
|---|---|---|
| 复核必须重跑 | 不复用对方任何中间值,用自己的环境跑全部命令 | “报告说 21/21,那应该就是 21/21” |
| 力气压在叙述类举证 | 可跑指标快速复现,叙述类逐条找物理文件 | 把复核时间花在重算分数上 |
| 修复前读自动化原文 | 列出全部定时任务,逐条读 prompt 正文 | 只看任务名字判断有没有冲突 |
| 修完必问副作用 | 消死链时有没有抹掉待建信号?改脚本时有没有别处会回退它? | “死链归零就行了” |
一次性检查清单
可以直接照抄:
| |
第 8 条是最容易被省掉、也最不该省的一条。如果你只有一个 AI 可用,最低成本的替代方案是:换一个模型、或者开一个不带上下文的新会话,只给它命令清单和报告原文,让它独立跑一遍。 不带上下文这件事本身就是价值——它不会重复你的思路,只会重复你的命令。
八、边界:这套做法适合谁,不适合谁
不适合:
- 只有几十篇笔记的库。 打开目录看一眼就知道了,上评分模型是过度工程。
- 没有任何人在用的库。 质量审计的前提是"有人在依赖它"。一个没人问的库,满分没有意义。
- 不想写文档的人。 这套东西的核心资产不是脚本——脚本随时可以重写——是那份写下来的评分模型,和每一次的报告。判断标准丢了,就再也重建不回来了。
适合:
- 有几百篇以上笔记、AI 在里面频繁读写、且**已经出现过"我以为改了其实没改"**的库。
- 有多个 AI 或多人协作、需要"完成"这个词有确定含义的场景。
- 准备把知识库的判断结果用于真实决策(报价、对账、谈判)的场景——这种时候,“数字可不可信"比"数字是多少"重要。
再回到开头那三个小时。
9.3 到 9.7 是三个小时。但支撑这三个小时的,是前面两个月攒下来的东西:一份写下来的评分模型、一套五态分离的状态定义、一个每天自动跑的记分卡、一条"生产者与复核者分离"的规矩。
那 0.4 分不是那三个小时挣来的。
那三个小时只是——第一次把这套东西,完整地用了一次。
延伸阅读(本地 AI 知识库实践系列):
