19—MD5 缓存让测评系统学会了推断,而不是询问

工具入口收敛之后,下一个问题不是继续加命令,而是让系统知道"现在处于什么状态"。如果 SKILL.md 没变、用例也没变,系统还每次都重新提炼规则、重新生成用例,本质上就是没有记忆。

MD5 缓存的价值不只是省时间。它让 SkillSentry 能根据 rules.cache.jsoncases.cache.json 和当前文件哈希推断状态,从而判断该复用、该重跑,还是该提醒用户确认。

这篇文章的关键判断有两个:

  1. MD5 哈希缓存的主要价值不是省时间,而是给系统提供状态感知能力。缓存记录的是"最后一次执行时的 SKILL.md 内容",比较当前内容和缓存,就知道"规则变了没有"。
  2. 让系统"推断你需要什么"比让用户"说清楚要什么"的可用性高一个量级。前者需要用户了解工作流的含义和区别,后者只需要说「测评 xxx」。


一、问题:每次都全量跑,即使什么都没变

SkillSentry 的完整流程里,有两个最耗时的生成阶段:

  1. 规则提炼:读取 SKILL.md,提炼出一套结构化的规则列表(用于后续用例设计的依据)。这一步需要 LLM 仔细阅读并归纳 SKILL.md 的全部内容,耗时约 1-2 分钟。

  2. 用例设计:基于规则列表 + inputs/ 里的外部用例,设计覆盖各类场景的 evals.json。quick 模式约 5-10 分钟,standard 模式更长。

问题在于:这两个阶段的输入是固定的,只要输入没变,输出就不会变。

如果 SKILL.md 没有任何改动,规则列表就和上次完全相同,再提炼一遍只是在重复已知的结论。如果规则列表没有变,设计出来的用例集也和上次相同(给定相同规则 + 相同 inputs/,LLM 会设计出高度相似的覆盖场景)。

但原本的单体管道每次都全量跑,没有任何跳过的机制。改了一个工具调用参数名,确认主流程没崩,也要等完整的规则提炼 + 用例设计 + 执行,20-30 分钟。

这个问题不难解:内容没变,就用缓存。


二、第一层缓存:规则缓存(rules.cache.json)

缓存的内容

json 复制代码
{
  "skill_hash": "a3f8b2c1",
  "extracted_at": "2026-04-03T10:00:00Z",
  "rules": [
    "提交报销单前必须询问用户确认,用户拒绝时终止流程",
    "金额字段只接受正数,为0或负数时提示用户重新输入",
    "..."
  ]
}

使用逻辑

ini 复制代码
执行开始前:
  计算当前 SKILL.md 的 MD5 → current_hash
  读取 rules.cache.json 中的 skill_hash → cached_hash

  if current_hash == cached_hash:
    直接加载 rules 列表,跳过规则提炼(节省 1-2 分钟)
  else:
    重新提炼规则,完成后写入新的 rules.cache.json

这一步本身不复杂。MD5 的计算是确定性的------相同的文件内容总是产生相同的哈希值。哈希命中意味着文件没有改动,规则列表可以直接复用。


三、第二层缓存:用例缓存(cases.cache.json)

规则缓存解决的是"规则没变就不用重新提炼"。但还有第二层:规则没变 + 已经设计过用例,那用例也不用重设计。

json 复制代码
{
  "rules_hash": "a3f8b2c1",
  "designed_at": "2026-04-03T10:05:00Z",
  "mode": "quick",
  "evals": [/* 完整的用例列表 */]
}

rules_hashrules.cache.json 里的 skill_hash 是同一个值------都是生成这套用例时的 SKILL.md MD5。

使用逻辑

ini 复制代码
cases 步骤启动时:
  计算当前 SKILL.md 的 MD5 → current_hash
  读取 cases.cache.json 中的 rules_hash → cached_hash

  if current_hash == cached_hash:
    直接加载 evals 列表,跳过用例设计(节省 5-10 分钟)
  else:
    重新设计用例,完成后写入新的 cases.cache.json

两层缓存加在一起效果显著:规则缓存命中可跳过提炼(节省 1-2 分钟);用例缓存也命中时直接切换到 regression 工作流(5-7 分钟),跳过整个用例设计和 quick 双轮执行。节省的部分全在规则提炼和用例设计阶段。


四、哈希比对如何工作

实现上,MD5 计算是一行命令:

python 复制代码
python3 -c "import hashlib,sys; print(hashlib.md5(open(sys.argv[1],'rb').read()).hexdigest())" /path/to/SKILL.md

输出是 32 位十六进制字符串(例如 a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5)。

在缓存文件里只存前 8 位就够了------用于展示时比较直观(a3f8b2c1),发生碰撞的概率可以忽略。

为什么 MD5 够用?

这里不需要密码学意义上的安全性,只需要「内容指纹」------能够可靠地检测文件是否发生了改变。MD5 在这个用途上完全足够,而且比 SHA-256 更短、更便于在 JSON 里展示和人工比对。

为什么不用文件修改时间?

修改时间(mtime)不可靠:复制文件、某些编辑器的保存行为、时区差异,都可能改变 mtime 而不改变内容。MD5 只看内容本身,跟文件系统元数据无关。


五、意外的副产品:状态感知

设计两层缓存时,我的目标是节省时间。但实现完之后发现了一个没有预期的副产品:

缓存文件是系统历史状态的快照。

rules.cache.json 存储的是「上次执行时 SKILL.md 是什么状态」,cases.cache.json 存储的是「上次设计用例时 SKILL.md 是什么状态」。把这两个文件和当前 SKILL.md 对比,可以得出四种不同的情况:

状态 含义
rules.cache.json 不存在 从未测评过这个 Skill
缓存存在,但 hash 不匹配 SKILL.md 有改动(规则可能变了)
缓存命中,cases.cache.json 也存在 规则和用例都没变,已有完整基准
缓存命中,cases.cache.json 不存在 规则没变,但还没设计过用例

这四种状态,对应四种截然不同的「此刻你需要什么测评」的答案。


六、四种状态 → 四种推断

状态感知的最大价值是:系统不再需要问你「要跑什么工作流」,它自己能推断出来。

复制代码
状态 1:rules.cache.json 不存在
推断:quick(首次测评,需要完整流程)
原因:从未执行过,没有任何基准,需要设计用例再执行
bash 复制代码
状态 2:hash 不匹配(SKILL.md 有变更)
推断:smoke(规则可能变了,先快速验证核心路径没崩)
原因:规则变了,之前的 Golden Set 可能不再反映当前逻辑。
      smoke 比直接跑 regression 更谨慎------先用 4-5 个新用例快速验证,
      确认没有断崖式崩溃,再考虑是否需要完整的 quick/standard
python 复制代码
状态 3:hash 匹配 + cases.cache.json 存在
推断:regression(规则和用例都没变,直接跑已有 Golden Set)
原因:SKILL.md 没动,之前设计的用例仍然有效,
      跑 regression 就能验证最近的改动没有破坏已有行为
bash 复制代码
状态 4:hash 匹配 + cases.cache.json 不存在
推断:quick(规则没变,但还没有用例)
原因:规则可以直接加载,省去规则提炼,但还需要设计用例才能执行

这个推断逻辑是基于「我知道你做过什么」来判断「你现在需要什么」------不需要你解释背景,不需要你记住工作流名字,只需要说「测评 em-reimbursement-v3」。

时间影响(典型场景,quick 模式基准):

场景 无缓存 有缓存(规则命中) 有缓存(规则+用例命中)
SKILL.md 未变,用例已有 ~20min(quick) - 5-10min(regression)
SKILL.md 未变,无用例 ~20min ~12min -
SKILL.md 有改动 ~20min(quick) - 5-7min(smoke,快速验证)

七、确认提示:推断不是强制,是建议

系统推断出工作流后,不会直接开跑。它会先输出一个状态确认:

javascript 复制代码
✅ 已找到被测 Skill:em-reimbursement-v3
   路径:~/.claude/skills/em-reimbursement-v3/SKILL.md

📊 状态检测:
  规则缓存:命中(hash: a3f8b2c1,规则 14 条)
  用例缓存:命中(共 10 个用例,设计于 2026-04-03)

→ 推荐工作流:regression
   原因:SKILL.md 未变更,已有 Golden Set,直接验证现有用例即可
   工具链:executor → grader → report
   预计时间:5-10 分钟

直接回复「开始」或不回复则 30 秒后自动开始。
如需调整,说:「full」「quick」「smoke」「regression」「lint」

这个设计有两个考虑:

透明:让用户知道系统做了什么推断,依据是什么,避免黑盒。如果推断结果看起来不对(比如你明明改了 SKILL.md 但缓存显示「命中」),用户能立刻发现并纠正。

可覆盖:推断是建议,不是约束。说「full」立刻切换到完整流程,说「smoke」就跑冒烟。一个词就能覆盖推断结果,不需要解释原因。

30 秒超时自动开始是为了 OpenClaw 场景(飞书消息触发)------异步场景下用户不一定实时看消息,推断通过就自动执行,减少等待。


八、诚实面对:缓存会误导吗

MD5 碰撞:理论上存在两个不同内容产生相同 MD5 的可能,但概率极低(约 2⁻¹²⁸),对这个用途可以完全忽略。

缓存的内容是「上次提炼的规则」,不是「SKILL.md 的未来变更」

这是缓存最容易被误用的地方。缓存命中意味着「SKILL.md 内容和上次提炼时相同」,不意味着「提炼出来的规则是正确的」。如果上次规则提炼本身就有遗漏(比如 LLM 漏掉了某条隐含规则),那这个错误会被缓存固化下来,每次命中都会用到错误的规则列表。

处理方式:强制刷新。说「full 测评」或「重新提炼规则」,系统会忽略缓存,重新走提炼流程。

cases.cache.json 只在 quick 模式的用例下有意义

如果上次是 smoke 模式(只设计了 4-5 个用例),cases.cache.json 里只有 4-5 个用例。如果下次推断是 quick(需要 8-10 个用例)但命中了 smoke 的缓存,会用不足的用例集跑 quick------这是个潜在的误判。

目前的处理是在 cases.cache.json 里记录 mode 字段,命中时检查 mode 是否和当前需求匹配;不匹配则重新设计。

缓存文件的更新时机

rules.cache.json 在规则提炼完成后立即写入,cases.cache.json 在用例设计完成后立即写入。如果执行过程中途中断(规则提炼到一半崩了),不会写入不完整的缓存文件,下次重新触发时直接重跑。这是文件写入原子性带来的自然保护。


两层缓存带来的最大收益,不是节省了那几分钟规则提炼的时间,而是让系统从「问你要什么」变成了「我知道你需要什么」。这个变化让「测评 xxx」成为了一个真正无歧义的入口------不管你是第一次测评、迭代改了几行规则、还是准备正式提测,系统都能根据当前状态给出一个合理的默认选择,而你只需要确认或覆盖。


相关推荐
ZJPRENO3 小时前
成本直降 80%!豆包 2.1 Pro 问世,海外高端模型性价比优势全无
aigc
ServBay1 天前
如何利用本地技术栈构建 0 成本 AI SaaS 雏形
后端·aigc·ai编程
ClouGence1 天前
零代码自动化测试:手把手教你录出一条能反复用的测试用例
前端·测试
RainmeoX1 天前
Gemma 4 情绪分类微调实录:AMD ROCm 单卡 + LoRA 全流程
aigc
leeyi1 天前
Deer-Go:字节 Deer-Flow 的 Go 移植,深度研究 Agent 全拆解
go·aigc·agent
threerocks1 天前
AI编程的商业模式已经在互联网大厂跑通了
程序员·aigc·ai编程
PetterHillWater1 天前
基于page-agent实现UI自动化测试
测试
怕浪猫1 天前
第3章 记忆系统:构建Agent的长期与短期记忆
aigc·openai·ai编程
DigitalOcean2 天前
AI 推理采用本地 + Serverless 混合架构:让敏感数据不出户,算力成本更低
aigc·agent