
30 个 Skill 堆在仓库里半年最怕别人问我一句话这些技能哪个真的好用以前我的回答基本靠嘴硬——社区下载的说 README 写得漂亮自己写的说当时花了一整夜调试改过的说已经修过三轮。直到上个月我实在看不下去动手写了一个给 Skill 做“体检”的小工具才把自己这点底子彻底照了一遍。结果第一次跑完就有点绷不住30 个 Skill8 个拿了 0 分。更扎心的是我一个个手动复核的时候发现这 8 个 0 分里大部分是“假 0 分”——Skill 本身没实质问题是体检器的设计有缺陷、测试环境没配好、评分标准太死板把好货误杀在起跑线上。这篇文章就聊清楚三件事Skill 质量评估该怎么设计体检器初版有哪些坑以及 8 个假 0 分到底是怎么被“冤枉”的。不管你是自己写 Skill 的还是从社区拉了一堆 Skill 回来囤着的这篇都应该能帮你少走不少弯路。1. Skill 评估为什么不能靠感觉30 个技能包的真实困境1.1 你攒的 Skill可能只是个“能跑”的摆设先对齐一个概念。这里说的 Skill指的不是某一个平台专属功能而是 Agent 生态里普遍存在的一种“可复用技能包”由结构化指令、参数定义、触发规则和可选脚本组成让大模型在特定任务场景下按固定契约执行。Codex 里有 skill豆包里装了技能Spring AI 生态也有类似的 skill 机制本质上都是同一件事——把高频、重复、有明确流程的能力封装成一个可被 Agent 调用的模块。这就像手机里的 App装一个“翻译”App理论上你就能获得翻译能力。但装上了不代表它能正常调起摄像头能调起摄像头不代表翻译结果不跑偏。Skill 也一样从 GitHub 拉下来只是第一步真正的问题是这个 Skill 的触发词命中率高不高、输出稳不稳定、遇到边界情况会不会直接崩。这些东西光看 README 是看不出来的。我自己这 30 个 Skill 的来源很杂有的是社区热帖里推荐的编号 Skill说得很玄乎有的是从 GitHub 仓库拉回来的“开箱即用”版还有几篇是我自己根据业务需求写的。当时每一个都有“非留不可”的理由但真正要我给它们排个优先级、说清哪个最值得信赖我发现自己根本答不上来。说白了就是零量化。1.2 “全凭感觉”的评估到底错在哪凭感觉判断一个 Skill 好坏典型路径是这样的看 README 的截图感觉不错跑一个精心挑选的 Demo 感觉能用再随便试一个别的输入发现结果不对于是得出结论“这个 Skill 不行”。这套流程最大的问题不在于结论对不对而在于它不可复现、不可对比、不可解释。举个真实例子。我之前有一个处理日志的 Skill用 Demo 测试时输出很漂亮结构化 JSON 一步到位。直到某天线上任务传给它一段编码异常的数据它直接返回了一堆乱码和报错堆栈。问题是这算是 Skill 的问题还是输入的问题还是我调用方式的问题凭感觉的时候你根本分不清最后只会把账记在 Skill 头上。另一个问题是“幸存者偏差”。测试时只会挑那些自己熟悉的数据去喂真正的长尾场景、异常场景全被绕过去了。而一个 Skill 在真实环境中能不能立住往往恰恰取决于这些边界情况。所以我才下定决心做一个“体检器”把所有 Skill 都拉到同一套标准下跑一遍用脚本打分把“感觉”这个东西挤出评估流程。2. 体检器初版设计五个维度加一条流水线2.1 体检维度怎么定五个看得见摸得着的指标设计体检器的第一步是确定从哪些角度给 Skill 打分。我最终选了五个维度核心原则只有一个每个维度都必须能被量化不能出现“还不错”“还行”这种模糊结论。体检维度考察内容打分方式可用性Skill 能否被正常加载和实例化是否缺依赖、缺配置自动加载测试加载失败直接 0 分并终止触发命中率在合理的用户输入下Skill 是否正确触发而非沉默或无响应用标准测试集跑 20 轮统计触发成功占比任务完成度实际执行结果是否达到预期目标是否返回了有效内容由评测模型打分 人工抽检核对稳定性同一输入多次执行结果是收敛还是飘忽不定相同用例跑 5 次计算输出一致性资源消耗平均 token 消耗、调用耗时、是否有明显浪费记录每次调用的 token 数和耗时取中位数这五个维度里稳定性是最容易被忽视的。我在体检过程中发现不少 Skill 在第一次执行时表现完美但同样的问题再问一遍结果就南辕北辙。大量模型输出天然有随机性Skill 的提示词设计得好不好直接决定了这种随机性会不会被放大。样本跑 5 次不是拍脑袋定的太少看不出波动太多成本扛不住5 次取中位数是实测下来性价比最高的方案。每个维度的满分为 20 分总计 100 分。最终评分用加权平均而不是简单求和可用性和任务完成度权重最高各占 25%资源消耗占 15%其余两项各 15%。这样设计是考虑到一个“缺依赖根本跑不起来”的 Skill就算提示词写得再好也是废铁可用性必须有一票否决的分量。2.2 体检流水线从测试夹具到最终报告体检器的执行流程我把它做成了五步流水线。第一步扫描 Skill 仓库读取每个 Skill 的清单文件提取名称、版本、依赖声明和参数定义。第二步针对每个 Skill 生成测试夹具夹具里预设一组与该 Skill 功能匹配的典型输入。第三步按顺序执行加载测试和功能测试记录全部原始输出。第四步把测试结果交给评测模型打分同时生成打分理由。第五步人工抽检低分项目把所有得分整理成一份体检报告。# 体检器核心打分逻辑简化版 def evaluate_skill(skill, test_cases): scores {} scores[availability] check_load(skill) if scores[availability] 0: return {total: 0, reason: skill load failed} hit_count 0 completion_scores [] outputs [] for case in test_cases: result run_case(skill, case) outputs.append(result) if result.triggered: hit_count 1 completion_scores.append(result.completion) scores[trigger_rate] hit_count / len(test_cases) * 20 scores[completion] average(completion_scores) * 20 # 稳定性同一用例重复执行 5 次 stability run_repeated(skill, test_cases[0], repeat5) scores[stability] stability * 20 avg_tokens average([r.tokens for r in outputs]) scores[cost] cost_score(avg_tokens) total weighted_total(scores) return {total: total, detail: scores, reason: build_reason(scores)}这段代码是初版的核心逻辑看着简单实际调试时踩了一堆坑。最典型的就是run_case这一步各个 Skill 的输入格式千奇百怪有的要 JSON有的要纯文本有的要带上下文历史。初版我用了一套统一的输入模板去喂结果后面复盘时发现光这一步就冤杀了至少两个 Skill。3. 8 个“假 0 分”复盘体检器先自曝了三个设计缺陷3.1 四个板子都打在谁身上0 分 Skill 的真实分类第一次全量体检跑完8 个 Skill 拿到 0 分挂得整整齐齐。我没有直接修改报告而是把每个 0 分的日志都翻出来手动复核了一遍最后发现这 8 个 0 分可以分成四种完全不同的情况。第一种是环境缺失型有 3 个。这几个 Skill 依赖外部 API但体检器运行时没有注入对应的 Key 和环境变量调用直接返回 401 错误。把 Key 配好之后重跑其中一个成绩直接到了 85 分。这类误杀最冤枉Skill 本身是好的死在了前置条件上。第二种是参数错位型有 2 个。它们的清单文件里明明白白定义了输入 schema规定必须传一个包含特定字段的对象。而体检器的测试夹具完全没有读这份 schema只是按通用模板塞了一段文本Skill 接收到不认识的格式直接拒绝执行。这种问题很像你把一个 Type-C 插头硬往 Lightning 口里怼接口不匹配东西本身没坏。第三种是输出偏好型有 2 个。这两个 Skill 的设计目标就是输出自然语言结论比如根据一段日志给出分析判断而不是返回结构化 JSON。体检器的打分模型却把“是否输出结构化结果”作为任务完成度的重要判断依据于是把“没按格式来”误判成了“没完成任务”。第四种是主动放弃型只有 1 个。它在我设计的测试样例面前直接返回了“输入信息不足无法执行任务”。手动核对时发现这个 Skill 内部设计了信息充分性校验输入缺失时宁可停止也不乱猜。从产品角度看这是一个很好的自我保护行为但在体检器里被当成了任务失败。3.2 体检器为什么会误判三个设计缺陷的反省复盘完这 8 个 0 分归根到底就三句话没有检查前置条件没有遵循输入契约没有区分任务类型。第一个缺陷是环境预检完全缺失。初版体检器拿到 Skill 就直接跑不去检查它的依赖声明也不检查环境变量是否就位。一个需要外部 API 的 Skill在缺 Key 的环境里跑报错其实是必然的但这个错不代表 Skill 质量有问题。后来我在加载测试前面加了一层环境预检模块专门解析 Skill 的依赖要求缺失项单独列出报告不再直接计入质量分。第二个缺陷是用一套通用输入格式喂所有 Skill。每个 Skill 都有自己的输入契约有的要求结构化字段有的要求特定前缀有的需要多轮对话上下文。初版偷懒统一处理结果就是大量参数错位Skill 不是不会做而是接收到的材料根本没法做。正确做法是让测试夹具先读取 Skill 的 schema再动态生成匹配的输入样例。第三个缺陷是评分标准过度偏向结构化输出。最初我觉得结构化输出就代表工程化水平高、更可控所以把输出格式纳入了任务完成度的判断。但不同类型的 Skill 有不同的正确打开方式——分析建议类 Skill 的价值就是那段自然语言结论你非要它输出 JSON反倒限制了它。后来我把“是否结构化”从通用评分标准里移除了改为按照 Skill 类型分轨道评估。3.3 破案之后体检器从“打分机器”变成“带诊断的体检”搞清楚误杀原因之后我把体检器的逻辑改了一版。核心变化有三处。第一处是增加“契约校验”环节。体检器在加载 Skill 后第一步先读取它声明的输入输出格式检查测试夹具是否匹配。契约校验不通过时直接报“测试夹具错误”而不是给 Skill 打 0 分。第二处是给每个低分结论附上完整的“打分原因链”。以前只给一个总分现在会把每一步的判断依据都记录下来加载时发生了什么、触发了没有、输出了什么、评测模型为什么给了低分。这个改动非常重要因为有了原因链我才能在误判发生时快速复位责任。第三处是增加人工复检名单机制。总分低于 30 分的 Skill 不再直接定案而是自动进入待复核列表。我每天抽半小时过一遍这些异常项确认是真实质量问题还是评估误差。说白了体检器是筛选器不是法官最终签字的还得是人。这一版改完后再跑全量8 个 0 分里恢复了 5 个剩下 3 个确认是真有问题其中一个是调用外部服务时逻辑有死循环另外两个是核心提示词写得过于模糊模型根本不知道要干什么。4. 实操中的高频问题与排障经验速查4.1 问题速查表10 个烦人但常见的坑体检器跑了这么多轮我顺手整理了一份常见问题的排查对照表基本覆盖了 Skill 评估中 90% 的突发状况。现象常见原因排查与修复建议加载直接失败缺少依赖包或文件路径错误检查 Skill 清单里的依赖声明确认相对路径基准调用返回 401/403环境变量未注入或 Key 过期在测试夹具初始化阶段注入所有声明过的环境变量结果为空字符串模型输出被误判为结束标记检查 Skill 是否自带终止 token 判断逻辑触发词没响应触发规则与测试输入不匹配核对测试输入是否包含 Skill 声明的触发关键词输出严重不一致提示词缺少强约束锚点同一用例跑 5 次比对关键结论是否漂移每次都超时API 超时设置短于实际响应时间将重试和超时参数提升到 180 秒再观察结构化解析失败输出包含 Markdown 代码块包裹在解析前先剥离代码块标记再执行 JSON 解析明明该拒绝却硬答Skill 未定义输入校验逻辑参考“主动放弃型”Skill 增加信息充分性判断分数在及格线反复横跳评测模型偏好影响打分固定评测模型的采样参数并抽样人工复核复测时分数差异大测试样例顺序影响上下文每个用例独立跑不共享会话历史4.2 比 0 分更值得警惕的陷阱假满分与运气分给 Skill 体检这件事真正难的不是识别 0 分而是识别假的 100 分。0 分好歹会逼着你去查原因高分之下的隐患反而容易被忽略。我遇到过一个典型的“运气分”案例。某个 Skill 输出听起来很有条理评测模型给了 90 分但人工复核时发现它总共只输出了三条泛泛而谈的通用建议完全没有针对输入内容做具体分析。评测模型被流畅的文字迷惑了直接打了高分。后来我在评测提示词里加了硬性要求打分前必须引用输入中的原文佐证结论没有具体引用就视为未完成任务。这一步把好几个虚高的分数打回了原形。另一个容易被忽略的是“稳定性的假象”。如果你把同一个 Skill 放在同一段上下文里反复测试它很可能表现得非常稳定因为这本质上是在测试模型的记忆能力而不是 Skill 的引导能力。真正有效的稳定性测试是构造同一意图、不同表达的输入看输出结论是否一致。就像一个老师同一道题换个问法学生还能给出正确解答那才是真的掌握了知识点。4.3 体检结果怎么落地不是扔报告而是改配置体检器给出一堆分数之后最考验人的环节才开始怎么把这些分数变成改进动作。我的做法是把低分 Skill 分成三类处理。第一类是配置就能救的比如缺环境变量、缺超时配置、依赖版本不匹配。这类问题直接在体检报告里标注“修复建议”和“预估耗时”能改的当场改掉。第二类是提示词需要调整的比如任务指令不清晰、边界条件没说明、输出格式约束不够强。这类我会把测试失败时的原始输出拼进报告里作为提示词优化的对照素材。第三类是设计思路就有问题的比如 Skill 试图覆盖太多任务导致指令互相冲突或者方案本身没有可实现的评估路径。这类该砍就砍不要心疼留着只会增加后续维护负担。5. 体检器下一步怎么走以及我踩坑之后的真实体会5.1 改造计划从“一次性体检”升级到“分级持续体检”现在的体检器还是一个“定期拉出来跑一遍”的批处理工具下一步我打算把它改成三个级别的持续体检机制。基础体检做的是“能不能用”这件事在上线前自动执行包括环境预检、契约校验和一次冒烟测试。功能体检做的是“好不好用”这件事每周跑一次完整的功能测试集覆盖常见路径和边界路径输出稳定性趋势和成本变化曲线。压力体检则是按需执行的深度健康检查模拟长时间多轮对话、异常输入注入和资源耗尽场景用来发现那些在正常测试中根本暴露不出来的问题。另外一个明确要做的改进是把“性价比”纳入评估体系。两个 Skill 都能完成任务但一个平均消耗 2000 token另一个只要 600 token差距在频繁调用时会被放大得很夸张。这里我打算引入一个效率系数用“有效信息密度”来度量输出质量与成本之间的关系避免高分 Skill 靠堆字数凑结果。5.2 我最大的体会别迷信分数要看“打分原因链”如果你也要做类似的 Skill 评估我的第一条忠告是任何分数都只是线索不是判决。分数背后那一串原因链才是真正有价值的东西。一次加载失败可能是 Skill 的问题也可能是环境的问题一次输出很差可能是提示词的问题也可能是测试输入根本不该喂给它。把原因链记录清楚你才可能在误判发生时快速复位责任而不是拿着错误结论去改一个本来没毛病的 Skill。第二条忠告是评估是持续的不是一锤子买卖。模型在升级依赖在变化同一个 Skill 上个月表现很好这个月可能因为底层模型输出风格的变化而明显退化。体检器真正的作用不是给你一次性的结论而是建立一条持续观测的基线每次跑完对比之前的数据谁在退化、谁在变好一目了然。最后说一个让我印象最深的小细节。复盘那 8 个假 0 分的时候我突然意识到那个“主动放弃型”的 Skill 是最冤枉的但也是我最欣赏的。它知道自己不知道所以直接说“信息不足无法执行”。在所有人都鼓励模型硬着头皮编答案的环境里一个 Skill 能被设计成“知止”反而说明它的作者是真的在用工程思维做东西。体检器要评估的不该只是产出能力还得包括这种“正确拒绝”的边界感。这次体检对我最大的价值不是给 30 个 Skill 排出了名次而是让我想明白了一个好 Skill不是永远不变地输出满分答案而是在合适的边界内稳定地做对事在边界外坦然承认自己做不到。