做 AI 客服、AI 助理的团队,通常会在第一年踩同一个坑:知识库不是不够大,而是不准。
不准的形态很有欺骗性——文档一份没少,搜索也搜得到,模型照常回答。只是答案里的那条「现状」是三个月前的。更麻烦的是,同一条现状往往不止一份副本:状态库一份、速查表一份、凭证表一份、操作手册一份、某个 agent 的本地记忆再一份。你改掉自己看到的那一份,剩下的继续把旧值喂给模型。
我们做过一次这样的治理:一条「某台机器常驻模型是 14B」的过时描述,最终在十几份文档里被定位到,而真正需要改的只有其中一部分——因为另一部分文件是历史记录,改了反而毁掉史实。这篇文章把那次的方法、判据和踩到的四个坑完整写出来,你可以在自己的知识库上照做。
单个错误好改。难的是三件事同时成立:
~/.openclaw/SHARED_MEMORY.md,有些在某个 agent 的记忆文件里。所以治理目标不是"把所有 14b 改成 9b",而是:让「当前事实」只有一处权威源,其余引用都指向它;历史记录保持原样并明确标注为当时事实。
| 问题形态 | 典型表现 | 错误处理方式 | 后果 |
|---|---|---|---|
| 多副本 | 同一条现状散落在十几份文档 | 只改被指出的那一处 | 其余文档继续误导模型与 agent |
| 历史混入 | 历史日志里有旧值 | 全库一键替换 | 毁掉史实,无法回溯"当时为什么这么判断" |
| 宽匹配误伤 | "留盘兜底 14b"其实是正确内容 | 按关键词一刀切 | 把对的改成错的,产生新错误 |
| 验证缺失 | 脚本说"已更新" | 以脚本输出为准 | 改了本地没下发,节点读到的还是旧版 |
这是整套流程里最容易被跳过、也最不能跳过的一步。当有人说"这里写错了",正确反应不是打开文档改字,而是先去测出真相。
那次争议点是"某台机器常驻的模型到底是 14B 还是 9B"。文档说 14B,另一份文档说 9B。谁对?两条路都不走——直接上机器测:
# 1) 当前实际加载的模型(不是"盘上有哪些",是"正在跑的")
ollama ps
# 2) 盘上有哪些模型
ollama list
# 3) 显存占用,反证加载尺寸是否与声称的参数量匹配
nvidia-smi --query-gpu=memory.used,memory.total --format=csv
# 4) 服务启动参数里配置的默认模型
systemctl show ollama -p Environment
结论是 9B。更要紧的是第二个发现:五台节点上的 agent 配置里写的全是 9B,只有知识树文档写 14B。
这个发现直接改变了修复范围——它证明"代码侧没错,错在文档层",于是可以放心地把改动锁死在文档里,不用去动任何运行配置。如果反过来先改配置,就会把五台跑得好好的机器改坏。
只搜错误关键词是半个动作。完整的做法是同时搜两个方向:
cd /path/to/knowledge
# 方向 A:搜错误表述(换关键词,排除归档与 .bak)
grep -rn "关键词A\|关键词B" --include="*.md" --exclude-dir="_ARCHIVE" . \
| grep -v "\.bak"
# 方向 B:搜「正确表述」,确认权威版本长什么样、哪些文件已经是对的
grep -rn "qwen3.5:9b" --include="*.md" . | grep -v "\.bak"
方向 B 的价值有两个:一是拿到"正确写法的样本",改的时候照抄措辞,避免二次口语化;二是找出一批可以当参照的文件——它们已经是 9B,说明这批文件此前被维护过,可信度更高。
然后把命中结果分两类,这一步决定了后面能不能放手让脚本跑:
| 分类 | 目录示例 | 处理 |
|---|---|---|
| 活文档 | 01_状态库/(拓扑与状态)、03_资料库/(速查与规范)、00_凭证库/(节点表)、02_经验库/ 里的指南类、05_工具库/ 操作手册 | 改 |
| 历史记录 | 01_状态库/历史日志/、02_经验库/历史交接/、INBOX_迁移_*.md、带日期的一次性日报 | 不改,它是「当时的事实」 |
这条分界不是洁癖。历史日志存在的意义就是回答"当时为什么这么判断"。把它改成今天的值,等于把事故复盘材料洗成了正确的历史——下次再出同类问题,你将没有任何第一手依据。
十几份文档、几十处替换,手改必然漏。正确做法是写脚本,但脚本必须带一道"闸门":每条规则都要预先声明它在文件里应出现的次数,实际命中数不符就整体中止,一个字节都不写。
RULES = {}
def add(rel, old, new, exp=1):
"""rel=文件相对路径, old=原文片段(必须够长且全文件唯一), new=替换值, exp=应出现次数"""
RULES.setdefault(rel, []).append((old, new, exp))
add("01_状态库/设备与拓扑.md", "常规任务走本地 qwen3:14b", "常规任务走本地 qwen3.5:9b")
# dry-run:任何一条 count(old) != exp → 抛错并整体不写
# --apply:先 copy2 生成 .bak-YYYYMMDD-<主题>,再写
三个细节决定这道闸门有没有用:
qwen3:14b 会命中"留盘兜底"这类正确内容;写上"常规任务走本地"这半句,唯一性就有了。exp=2;写 1 会报错中止——这恰恰是好事,说明你对文件的认知还不准。经验值:这道闸门在实际操作中挡掉了绝大多数误改,而且挡住的方式很友好——它不是改错了才报错,而是在写之前就停下,让你回去把规则写清楚。备份统一用 .bak-YYYYMMDD-<主题> 后缀,写在同一目录,方便回溯。
改完本地文件只完成了一半。知识点散布在多台机器上,每台读的是自己那份同步副本,所以必须验证"改动能被下游看到"。
1)在权威状态文档顶部加「更正块」,而不是删掉旧描述。格式固定:标题写 # 📌 YYYY-MM-DD 更正:<一句话>,内容包含背景、实测证据(原始命令输出)、变更时间、现状表,最后一句声明:"本文件其余旧表述均为当时史实,涉及现状一律以本块为准。" 旧块不删——知识库的历史是靠"新块覆盖旧块"累积的。
2)下发,并读下发脚本的真实计数。健康的输出长这样:待推送 N / 已一致 M / 失败 0。重点不是 N 有多大,而是失败必须是 0,且"已一致"的条数要与上一轮相比合理——如果昨天推了 30 条、今天"已一致"还是 5 条,说明同步链路有问题。
3)逐节点验证"新词可见"。不要只在一台机器上 grep。每台挑 3~5 个代表性文档,grep 新值,期望命中数 =1;再挑 1~2 个历史文档 grep 旧值,期望仍然命中(证明没误改历史)。后者很少人做,但它才是"没有改坏"的证据。
收尾扫残留时用了 主力.*14b,结果命中一条"留盘兜底 qwen3:14b"。这条内容是对的——14B 确实还在盘上做兜底,错的是"常驻/主力/默认"。同一个模型名,"在盘上"和"正在跑"是两件事。
判据:宽 pattern 的命中必须人工逐条看,不能当布尔值用。判断"这条要不要改"的标准是它在陈述现状还是陈述库存,不是它含不含旧值。
在 Windows 的终端里 grep 中文路径,输出可能变成 01_鐘舵€佸簱 这种形式。它不影响 grep 是否命中,但会严重误导你对"改的是哪个文件"的判断——尤其在你按输出路径去写规则表的时候。
做法:判定与写盘一律用 Python 读文件、用 Python 打印路径;终端里的 grep 只用来"看有没有",不用来"确定是哪个"。
这次的真相是五台机器配置全是新值,只有文档写旧值。如果反过来——配置是旧的、文档是新的——那就不是文档治理,而是配置变更,风险等级完全不同。
做法:动手前必须先回答一个问题:这次是"文档落后于现实",还是"现实没跟上文档"? 前者是改文字,后者是改系统。混淆这两者,是把自己送进事故。
每台机器上还有一份 ~/.openclaw/SHARED_MEMORY.md。节点读的是自己本地那份,不是网络盘上那份。只管中心知识库,就会得到"中心已经是新值、节点还在按旧值干活"的分裂状态——而且它不报错,只是行为慢慢变得不对。
做法:治理清单必须包含"每台的本地记忆副本"这一项,验证时也必须在每台上 grep。
这套流程适合"现状描述类"的事实:谁在哪台机器上跑什么、哪个服务在线、某个默认值是多少。它不适合三类内容:
| # | 动作 | 通过判据 |
|---|---|---|
| 1 | 实测真相 | 有原始命令输出,且能独立复现 |
| 2 | 判定改动层级 | 明确写出"是文档落后,还是配置落后" |
| 3 | 全量 grep 双向 | 错误表述与正确表述的命中清单都在手 |
| 4 | 活文档 / 历史记录分类 | 每份文件都归入其中一类,无"待定" |
| 5 | 规则表声明 exp | 每条 old 全文件唯一,exp 与实测一致 |
| 6 | dry-run | 全部命中数相符,否则整体不写 |
| 7 | 备份后写入 | 每份改动文件都有 .bak-YYYYMMDD-主题 |
| 8 | 加更正块 | 含证据、时间、现状表、"以本块为准"声明 |
| 9 | 下发 | 失败 = 0,"已一致"计数合理 |
| 10 | 逐节点验证 | 新值每台 =1;历史旧值仍命中 |
| 11 | 本地副本 | 每台 ~/.openclaw/SHARED_MEMORY.md 已查 |
| 12 | 残留复查 | 宽 pattern 命中逐条人工判定,不做机械替换 |
最后一句实话:知识库治理真正难的不是脚本,是忍住不"顺手全改"。历史记录、库存描述、观点分歧,看起来都写着旧值,但它们不该被改。能分清"哪一处是事实、哪一处是史实、哪一处是观点",这件事就完成了一大半。
如果你的 AI 客服/助理出现过"答的是上一个版本的流程""明明改过但模型还按旧的说",通常不是模型问题,而是知识库存在多副本与过期条目。我们可以做一次知识库一致性体检:全量定位、分层分类、输出带命中数的修正规则表,并给出逐节点可见性验证结果。
如果这条信息只出现在一个文件里,是的。但真实情况通常是同一条现状在十几份文档里被复述,措辞各不相同。只改你看到的那一处,其余副本会继续把旧值喂给模型,表现为"改了但没生效"。所以第一步不是改字,是先全量定位有几份副本。
两个原因。一是会误伤历史记录——历史日志里写"当时是 14b"是正确的,改了就毁掉史实,以后无法复盘。二是会误伤正确内容——比如"留盘兜底 14b"这种描述是对的,模型确实还在盘上,只是不是"正在跑的那个"。机械替换会把对的改成错的。
每条替换规则都要预先声明"这段原文在文件里应该出现几次"。脚本先只统计、不写盘,任何一条实际命中数与声明不符就整体中止。这道闸门的作用不是发现改错,而是在写之前就拦下来,逼你把规则写准确。实践中它能挡掉绝大多数误改,包括文件被别人改过导致的静默空过。
不算。知识点通常分布在多台机器上,每台读的是自己那份同步副本,还有一份本机记忆文件(如 ~/.openclaw/SHARED_MEMORY.md)。必须下发后逐台验证"新值可见",同时验证"历史文档里的旧值仍然命中"。只验证前者,会漏掉"改坏历史";只验证中心库,会漏掉"节点还在按旧值干活"。
因为这个知识库的历史是靠"新块覆盖旧块"累积的。顶部更正块写清背景、实测证据、变更时间和现状表,并声明"其余旧表述均为当时史实,涉及现状以本块为准"。这样既能立刻纠正现状,又保留了"三个月前为什么判断成那样"的原始依据。删掉旧描述,等于把事故复盘材料洗成了正确的历史。
看它在陈述什么。"某台机器正在跑哪个模型"是现状,必须改;"盘上有哪些模型"是库存,只要没变化就不改;"当时为什么选 14B"是史实,不能改;"14B 比 9B 更适合这个场景"是观点,保留并标时间。分不清这四类,就会把一次治理做成一次污染。