24—AI Skill 测评工作流工具箱化:为什么 regression 会自然出现

上一篇把确定性逻辑从 Prompt 中拆出来,解决的是"什么该由代码负责"。本文继续看工具边界:当主流程已经足够清楚之后,哪些能力应该保持为可组合工具,而不是永远绑在一条从头到尾的管道里。

这篇不是讲"我们怎么拆工具",而是讲一个更通用的问题:什么时候应该把一条固定流程拆成可组合工具箱。

SkillSentry 原来的单体管道只有一种使用方式:从头跑到尾。这个设计能保证完整性,但也带来一个问题:日常开发里最常见的需求并不是"每次全量重测",而是"我只改了一条规则,能不能只验证受影响的用例"。

工具箱化之后,最大的收获不是速度,而是自然浮现出一个原本不可能存在的工作流:regression。它跳过用例设计,直接复用已有 Golden Set 和历史失败用例,正好对应日常修复后的验证需求。

本文的核心方法是:把管道拆成五个原子能力(sentry-static / cases / executor / grader-report / sentry-report),用 JSON 文件而不是函数调用传递状态;薄编排器只负责调度顺序,不承载业务逻辑。


核心结论有两个:

text 复制代码
1. 工具箱化最大的收获不是速度,而是让 regression 这种按需工作流变得可能。
2. 在 LLM orchestration 里,工具间通过文件而不是函数调用传递状态,反而更利于恢复、替换和检查。

本文最重要的发现不是"测评变快了",而是:把单体管道拆分成原子工具后,自然涌现出了一个我们从来没有专门设计过的工作流------它不可能在单体管道时代存在,却在工具组合之后自然出现了。这个发现比所有性能数据都更值得记录。


一、先说清楚问题:不是「慢」,是「绑死了」

SkillSentry 最初是作为一个完整测评管道设计的:

css 复制代码
规则提炼 → 触发率评估 → 用例设计 → [L1 执行] → [L2 Grader] → [L3 Comparator] → 报告

很长一段时间里,我以为主要问题是「太慢了」,于是做了一轮性能优化:MD5 哈希缓存(跳过规则重提炼)、mega-batch 并行、Grader 批量调用、上下文压缩......优化完,quick 模式从 30 分钟降到了 8-10 分钟(mcp_based)/ 15-20 分钟(其他)。

但用了一段时间之后,我发现更本质的问题不是速度,而是整个系统只有一种使用方式

三个高频场景,都被这个单一流程卡住了:

场景一(最常见):改了报销单提交逻辑里的一个字段映射,只想确认「主流程没有崩」。 系统的回答是:重新提炼规则 → 重新设计 10 个用例 → 每个用例跑 2 次 × 双侧 → Grader → 报告。20 分钟。 我需要的是:跑上次已经验证过的 5 个核心用例,5 分钟出结论。

场景二:刚写完一个新 Skill 的 description,想先确认触发率够不够,再决定要不要写功能规则。 系统没有这个能力,只能触发全量流程,等 20 分钟换来一个我根本不需要的完整报告。

场景三:Skill 上线前想做一次静态检查,确认 HiL 节点没有漏、规则不冗余。 同样不行。

这三个场景的共同症结不是「流程太慢」,而是:「工具」和「流程」被死死绑定在一起了------有没有 MCP 连接、是不是要跑完整 Grader、要不要出 HTML 报告,这些事情在用例设计那一步就已经被固定下来,用户没有任何选择空间。

工具箱化要解决的是这个问题,不只是速度。


二、拆分的前提:先定清楚每个工具的边界

「工具箱化」不是把一个大文件切成几个小文件。在动手之前,我强迫自己回答一个问题:

每个工具的输入是什么、输出是什么、它对外部状态有什么依赖?

这一步花了比写代码更多的时间,因为不想清楚这件事,拆出来的工具会悄悄依赖彼此的内部状态,表面上独立,实际上还是一条串联管道。

整理之后,得到了这张表:

当前工具 核心职责 输入 输出 能否独立运行
sentry-static 静态检查 + 触发检查 SKILL.md / description static-check / trigger 结果 ✅ 完全独立
cases 用例设计 rules + inputs/ evals.json ✅ 独立
executor 并行执行用例 evals.json transcript + timing ✅ 依赖 evals.json
grader-report 断言评审 + 汇总报告 + 发布建议 transcripts / grading 输入 grading-summary + report ✅ 依赖执行结果
sentry-report 已有 grading 后重新出报告 grading-summary / session report.html ✅ 特殊场景独立使用

这张表有一个关键列:能否独立运行 。每个工具的依赖必须是文件(evals.json、grading.json),而不是内存里的某个对象或另一个工具的运行时状态。这个约束决定了后来工具间数据接口的设计方向。


三、原子工具:为什么这样拆

拆分之后,每个工具都是一个独立的 本地 Skill,有自己的 description(用于触发检测)。

早期版本讨论的是"五个原子工具",稳定实现已经把其中一部分合并为更清晰的入口。保留下来的设计原则是:工具边界必须按输入/输出文件切,而不是按"看起来像一个步骤"的口头描述切。

少拆(比如把 cases + executor 合并)的问题:合并之后「只设计用例、不执行」这个场景就消失了------用例设计完了先 review、再决定要不要跑,这个中间状态就没了。

多拆(比如把评分和报告长期拆成两个常规步骤)的问题:报告强依赖评分结果,分开会增加一次 subagent 调度和一套接口维护开销。当前版本的主流程因此收敛为 grader-report;独立 sentry-report 只保留给"已有 grading,重新出报告"的特殊场景。

当前工具,按「是否需要真实执行」分成两类

不需要执行任何工具的(纯分析/生成)

  • sentry-static:静态读 SKILL.md,覆盖 description、HiL、复杂度、冗余规则、规则可测试性和触发检查,30 秒到 2 分钟
  • cases:读规则 + inputs/,双源合流设计用例,标注断言强度分级,输出 evals.json,5-10 分钟

需要执行的(启动 subagent)

  • executor:读 evals.json,并行启动 with_skill / without_skill(按当前 baseline 规则决定是否需要 without_skill),输出 transcript + timing,10-20 分钟
  • grader-report:读执行结果,完成断言评审、汇总指标、生成报告和发布建议
  • sentry-report:读已有 grading/session,重新生成报告,不作为主 pipeline 的常规评分步骤

特别说明 sentry-static:这个工具的最大价值是它不需要任何 MCP 连接。Skill 刚写完、还没有配置任何工具调用的情况下,30 秒就能发现 HiL 漏洞。最典型的漏洞:写了「提交前询问用户确认」,但没写「用户拒绝时怎么处理」------这是 HiL 检查项,静态分析就能发现,比跑测试用例快很多。


四、架构涌现:一个我没设计过的工作流

这是全文最重要的一节,也是我在动手拆之前没有预料到的。

拆分完成后,我在梳理「各种工具可以组成哪些工作流」时,发现了一个之前根本不存在的组合:

跳过 cases 设计,直接用 inputs/ 里已有的用例,跑 executor → grader-report。

这就是 regression 工作流。

为什么说它是「涌现」的,而不是「设计」的?

在单体架构里,这个工作流物理上不可能存在。SkillSentry 的主流程是:规则提炼 → 用例设计 → 执行 → 评审 → 报告,这五步是一条强制串联的流水线。没有任何地方可以插入「我已经有用例了,跳过设计这步」的选项。即使想到了这个需求,也无从实现。

拆成工具箱之后,工具是独立的,流程是编排器决定的,「跳过某个工具」变成了一个正常的选项。regression 工作流是架构本身给出的,不是我主动设计的。

它的价值在哪里?

看一下这张频率分布表:

场景 触发频率 改造前能用的工作流 改造后
修了一条规则,验证没有崩 最高频(每天多次) quick(20min,超杀) regression(5-10min)
迭代完成,准备提测 中频(每周数次) quick(合适) quick(不变)
正式发布前全量验证 低频(每次发布) full(合适) full(不变)

regression 工作流命中的是最高频的使用场景,而这个场景在单体架构里一直是被 quick 工作流(过度设计的流程)强行兜着的。

这个发现让我意识到:工具箱化的真正收益,不是给现有工作流提速,而是让之前被压制的工作流重新浮出来。单体架构把「测评」变成了一个不可分割的动作,工具箱化把它还原成了一组可以独立使用的能力。


五、JSON 文件是合约:LLM 工具链的接口设计

在设计工具间的数据传递方式时,我自然而然地选择了文件------工具 A 把结果写到 evals.json,工具 B 读取 evals.json。

做完之后才意识到,这个选择和传统软件工程的直觉是相反的

传统软件工程的建议是:优先用函数调用(参数类型安全、无文件 I/O 开销),文件耦合是反模式(两个模块通过临时文件传递状态,耦合不可见、难以测试)。

但对于 LLM 工具链,这个直觉在三个关键点上失效

① LLM subagent 之间没有共享内存

传统函数调用之所以好用,是因为调用方和被调用方在同一个进程里,可以传递内存引用。LLM subagent 不是这样的------每个 subagent 是一个独立的 API 调用,没有共享堆,「把对象引用传给另一个 subagent」这个操作根本不存在。你能传递的只有可序列化的数据。选择「函数调用」还是「文件」,在 LLM orchestration 里,本质上是在选择「直接 inline 所有数据」还是「用文件路径作为引用」。文件路径作为引用,反而是更轻量的方式。

② 文件让失败可恢复

一次 executor 批次中途崩了(网络超时、MCP server 不稳定),已完成的 eval 的 transcript.md 还在磁盘上。重新触发时,检查文件是否存在,存在则跳过,只跑未完成的部分。

用函数调用 + 内存传递,这个能力根本实现不了------上一次的运行结果在 subagent 退出时已经消失。

③ 文件让工具可替换

想换一个更快的 Grader 实现?只需要保证它输出的 grading.json 格式不变,其他工具完全不需要修改。

想加一个新工具(比如专门做「regression 对比」的工具)?只需要它能读 grading.json,往 workspace 目录写一个新 JSON 文件,就可以接入工作流。

这是「接口稳定性」在 LLM orchestration 里的具体形态:工具间的接口是文件格式,而不是函数签名。改变函数签名需要同时修改调用方;改变文件格式,只要旧格式的读者和新格式的写者都更新,过渡是可控的。

④ 文件让状态可检查

测评跑到一半,我怎么知道哪些 eval 已经完成了?哪些断言 failed?直接 cat sessions/xxx/eval-3/grading.json 就知道了,不需要 LLM 告诉我。

这是 LLM 工具链里一个经常被忽视的可观测性问题:LLM 的上下文不是永久存储,对话结束了状态就消失了。用文件持久化中间状态,是 LLM 工具链里「调试友好」的必要条件。


工具间的文件接口如下,每个文件的格式就是工具间的合约------写的一方不能随意改字段,读的一方也知道能依赖哪些字段:

sql 复制代码
rules.cache.json      ← SkillSentry 写,cases 步骤读
cases.cache.json      ← cases 步骤写,复用时 executor 间接受益
evals.json            ← cases 步骤写,executor 读
timing_with.json      ← executor 写,grader-report 读
timing_without.json   ← executor 写,grader-report 读
grading.json          ← grader-report 写,sentry-report 可在重出报告时读取
trigger_eval.json     ← sentry-static 写,grader-report 读(full 模式)
comparison.json       ← Comparator/Analyzer 写,grader-report 汇总读取(standard/full 模式)
eval_environment.json ← executor 写(并行率审计)

这张图有一个值得注意的地方:没有任何一个工具既是某个文件的读者又是另一个文件的写者(除了 SkillSentry 编排器本身)。每个工具的依赖链是单向的,没有循环。这不是意外,是在定义工具边界时强制保证的。

一句话总结 :在 LLM 工具链中,文件接口比函数调用更可靠,因为它解决了三个函数调用解决不了的问题:可恢复 (系统崩溃后可从文件继续)、可替换 (任何工具都能读写同一格式的文件)、可检查(文件内容随时可人工核验)。


六、编排器越薄越好

改造后,SkillSentry 主 SKILL.md 从 222 行精简到 154 行,execution-phases.md 从 600 行精简到 140 行。

被删掉的内容,全部迁移到了各工具自己的 SKILL.md 里------subagent steps 上限、transcript 双分离格式、Grader 调用规则、without_skill 早退指令......这些执行细节,和编排器没有任何关系。

编排器剩下的是:

markdown 复制代码
1. 识别用户意图 → 映射到工作流
2. 初始化工作目录 + 规则缓存检查
3. 按顺序触发工具,传入 workspace_dir
4. 工具通过文件传递状态(编排器不关心文件内容)

这里有一个反直觉的地方:编排器越薄,工具箱反而越好用

道理是这样的:如果编排器里包含了工具的执行细节(比如「Grader 每次必须处理 ≥2 个用例」),那每次 Grader 的规则有变化,都要改编排器。但 Grader 的用法只和 Grader 自己有关,应该封装在 agents/grader.md 里,不应该泄漏到编排器里。

当编排器只知道「调用 Grader」而不知道「Grader 具体怎么工作」,工具的内部实现就可以独立演进,不会每次改工具都要同时改编排器。

这和微服务设计里「API Gateway 不应该包含业务逻辑」是同一个原则,只是在 LLM orchestration 的语境下重新表述了一遍。


七、改造前后对比

时间变化(典型场景)

场景 改造前 改造后 减少
修了一条规则,验证主流程 20-30 分钟(quick,杀鸡用牛刀) 5-10 分钟(regression) ~70%
开发迭代冒烟验证 20-30 分钟(只有 quick) 5-7 分钟(smoke) ~75%
检查 SKILL.md 写法质量 不可能单独触发 30 秒(check 静态模式) 全新能力
验证 description 触发准确性 不可能单独触发 2 分钟(check 触发率模式) 全新能力
用例设计完先 review 再决定 不可能中途停 cases 步骤单独运行 全新能力
正式发布前全量验证 45 分钟+ 45 分钟+(无变化) ---

full 测评的时间没有变化------工具箱化优化的是「不必要的全量流程」,不是全量流程本身的速度。

文件体积变化

文件 改造前 改造后
SkillSentry/SKILL.md 222 行 154 行(-31%)
execution-phases.md 600+ 行 140 行(-77%)
各 sentry-* 工具 不存在 5 个,共约 500 行

execution-phases.md 变化最大。原来它承担了「Phase 1 触发率规范 + Phase 3 用例设计规范 + Phase 4 执行规范 + Phase 5 报告规范」四件事,600 行里任何一块的改动都需要理解整个文件的上下文。现在它只做一件事:定义工具间的数据接口,140 行,改任何一个 JSON 字段,影响范围一目了然。


八、诚实面对:工具箱化解决不了什么

工具间的接口变更是新的风险

用文件做接口,好处在前文已经说了。代价是:接口变更不会在编译期报错。如果 executor 步骤修改了 timing_with.json 的字段名,grader-report 在读取时会静默地得到 null,不会有任何报错,直到出了报告才发现数据缺失。

这个问题在传统软件里由类型系统和编译器解决,在 LLM 工具链里没有对应的机制,只能靠文档(execution-phases.md 里的接口定义)和人工纪律。

单独调用工具时,前置条件需要用户自己知道

executor 步骤需要先有 evals.jsongrader-report 需要先有执行结果,sentry-report 需要先有已有 grading/session。如果用户不清楚这些前置条件,会得到令人困惑的「文件不存在」错误,而不是「你需要先运行 cases 步骤」这样的友好提示。目前靠 description 字段做说明,但没有自动的前置条件检查。

regression 工作流的正确性依赖 Golden Set 的质量

regression 工作流跳过了用例设计,假设 inputs/ 里的 Golden Set 是足够好的。如果 Golden Set 本身有盲区(比如缺少某类边界用例),regression 通过了并不意味着 Skill 质量没有问题,只意味着 Golden Set 里的用例都通过了。Golden Set 的维护成为新的质量保证环节,这个责任从工具本身转移到了使用者身上。

LLM 的串行本能没有被根本解决

工具箱化改变了架构,没有改变 LLM 的执行倾向。executor 步骤里要求「with_skill 和 without_skill 必须在同一消息中并行发出」,这条规则依赖 LLM 自觉遵守。并行度审计(每批完成后计算 start_gap)让违规可见,但不能主动阻止串行。真正可靠的并行需要平台层的原生并发支持。


工具箱化给 SkillSentry 带来的最大价值,不是速度提升,而是让测评系统从「一个动作」变成了「一组能力」。这个变化让之前被压制的 regression 工作流有了存在的空间------而它恰好是日常开发中最需要的那一个。


九、工具箱化之后的新问题:可分发性

完成工具箱化之后我意识到,架构问题只解决了一半------工具能不能被别人用,是第二个关键问题。

工具箱化完成后,我系统地梳理了一次「普通人拿到这个工具,第一次使用的完整开销」。这个梳理过程本身就发现了几个问题------它们和架构无关,但对实际可用性的影响不小。

问题一:安装需要克隆 6 个仓库

工具箱化之后,独立 Skill 是独立目录。用户要装 SkillSentry,需要分别克隆 6 个目录到正确位置。这在开发者自己用的时候不是问题,但如果要分享给别人,门槛就出来了。

解法是把 sentry-* 的 SKILL.md 全部放进 SkillSentry 主仓库的 tools/ 子目录,再写一个安装脚本(install.sh + install.ps1)负责把各工具部署到正确位置。用户现在的安装步骤变成:

bash 复制代码
获取 SkillSentry 发布包后进入目录
cd SkillSentry && bash install.sh

脚本会自动检测系统里装了 本地编码助手 还是 OpenCode(或两者都有),部署到对应路径,最后输出验证结果。一次克隆,覆盖两个平台。

这个改动的工程量很小,但对「能不能传播出去」的影响是质变。工具再好,安装需要 6 步,大多数人会在第 2 步就放弃。

问题二:mcp_based Skill 测评可能出假阴性,但用户不知道

executor 步骤执行用例时会直接调用被测 Skill 依赖的 MCP 工具。如果 MCP server 没有启动,工具调用会报错,transcript 里一堆 error,Grader 会判 FAIL。

这个 FAIL 不是 Skill 的问题,是环境的问题。但对用户来说,收到的报告里写着「通过率 30%,建议修复」------他们会去改 Skill,而不是去查环境配置。这是测评系统最危险的一种失效模式:结论看起来可信,但结论的来源是污染的。

修复是在执行工作流前加一步 MCP 可用性预检:列出被测 Skill 引用的工具名,对照当前可用工具,不匹配时明确告警并让用户选择继续还是中止。这一步把「静默失败」变成了「可见失败」,代价是多一次工具调用,收益是结论的可信度。

问题三:使用开销不透明

用户说「测评 xxx」之前,不知道这次会消耗多少 token。quick 模式 10 个用例 × 2 次运行 × 双侧 = 20 个 subagent,单次消耗约 5-10 万 token。如果用的是按量统计的运行环境,这是真实的资源,应该在开始前告知。

修复很直接:在推断工作流的确认提示里加一行 Token 预估。信息本来就有(工作流决定了用例数和运行次数),只是之前没有输出给用户看。


这三个问题有一个共同的特征:它们只有在真实分发、真实使用的场景下才会浮现。 自己用的时候,知道 MCP 该怎么配,知道 6 个目录怎么放,知道这次大概跑多久------这些背景知识是隐含在脑子里的,不会感觉到有任何门槛。

工具设计从「我能用」到「别人也能用」,需要的不是更多功能,而是把这些隐含的前提条件一条一条显式化。


后续演进:工具箱化之后还要收敛契约

工具箱化解决了"能不能按需组合"的问题,但它不是终点。后续架构继续往两个方向收敛:

工具箱化阶段解决的问题 后续继续补强的方向
原子工具可以按需组合 Pipeline 状态机保证步骤不可跳过
文件作为步骤间契约 active-pipeline.json 支持断点续跑
静态检查、触发率、综合建议逐步合并 稳定入口收敛为 sentry-static
Grader 作为独立评审者 主流程收敛为 grader-report,评分和报告一次完成
CI 接入停留在概念层 sentry_ci.py 读取结构化 JSON 产物做门禁

核心思路没变:工具箱化、按需组合、文件作为步骤间契约。变化的是执行保障机制------从"依赖编排器记忆"升级为"状态机强制执行",解决长对话中 AI 跳步的结构性问题。

相关推荐
threerocks3 小时前
神级 Skill,作品个个儿爆,我开源了长期自用的手绘风格库
人工智能·aigc
leeyi4 小时前
Callback 系统:给 Agent 管道装上“监听器“
aigc·agent·ai编程
赫媒派4 小时前
OpenClaw 3 个提效设置实战:自动快模式、自适应思考、定时工作流
aigc
深蓝AI4 小时前
MCP 协议拆解:Claude Code 的工具调用背后发生了什么?
aigc
Momo__4 小时前
MDN MCP Server——Mozilla 把 Web 文档接进 AI Agent,从此 LLM 不再瞎编 API
前端·ai编程·mcp
kfaino5 小时前
码农的AI翻身(六)你好,我叫 Parameter
后端·aigc
kyriewen5 小时前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
猪猪拆迁队6 小时前
虚拟工厂仿真引擎的架构设计:让一条产线可编程、可观测、可干预
后端·ai编程
ZzT7 小时前
让 AI 少写一半代码:拆解爆火的 ponytail
ai编程·claude