资讯动态

Skill 工程化:从功能清单到可验证能力契约

发布时间:2026/9/10 2:47:00 来源:尧图企业网站定制
1. 为什么“Skill”这个词正在被用烂而你却还在盲目写 SKILL.md最近翻了二十多个开源 Agent 项目仓库几乎每个 README 下都挂着一个SKILL.md文件点开一看八成是这样的发送邮件查询天气调用 GitHub API生成 Markdown 表格——这根本不是 Skill这是四行 curl 命令的注释版。更离谱的是有团队把“用 Python 写个 for 循环遍历列表”也塞进taste-skill分类里还配了张 ponytail 女生插画当 banner。我当场关掉页面泡了杯浓茶冷静三分钟。Skill 不是功能清单不是 API 列表更不是 UI 按钮的说明书。它是一个可识别、可组合、可验证、可演化的最小能力单元其本质是在特定上下文约束下对一类语义明确的任务提供稳定、可观测、带边界定义的执行契约。你写下的每一行SKILL.md如果不能回答以下五个问题就等于在给 Agent 系统埋雷触发条件是什么是用户说“帮我查昨天的会议纪要”还是必须匹配正则^查.*会议.*[昨|今|明]天$输入契约是否封闭接收 JSON 还是自然语言字段名是否强制 camelCase缺失user_id是否拒绝执行输出形态是否可断言返回纯文本带结构化字段的 Markdown 表格是否必须包含## Summary二级标题失败边界是否明确定义超时 800ms 算失败API 返回 429 时重试几次遇到agent execution terminated due to error.是该降级还是抛异常演化路径是否预留接口v1.0 支持单日查询v1.1 要支持时间范围如何不破坏旧调用方这才是 Skill 的工程内核。它和agent.md的区别就像电路板上一个封装好的 IC 芯片 vs 一堆散装电阻电容它和codex skill的差异不在于是否用 LLM 生成代码而在于是否把“生成”本身作为可编排、可监控、可回滚的一等公民行为。我见过最典型的反面案例某团队用workbuddy skill名义上线了 17 个.md文件结果上线第三天因其中一份skill.md 目录设计里把“上传附件”和“解析 PDF 文字”混在一个 Skill 里导致用户上传 Word 后系统直接 panic —— 因为解析逻辑硬编码只认.pdf后缀连文件头 magic bytes 都没校验。这不是 Bug是 Skill 边界失守的必然结果。所以这篇文章不教你怎么写 Markdown 语法不推荐 chrome 查看 markdown 插件也不分析markdown preview enhanced 使用prince导出乱码这种工具链毛刺。我们要做的是回到原点用工程思维重新定义 Skill 的骨架、血肉与神经反射。接下来所有内容全部基于真实项目落地经验——包括我们自己踩过的 37 个坑、重构 5 次的 Skill Runtime、以及被 PM 追着改了 11 版的SKILL.md模板规范。2. Skill 的本质不是“能做什么”而是“在什么条件下以什么方式承诺做到什么程度”2.1 把 Skill 当作“函数签名”来设计而不是“功能描述”来罗列很多团队写SKILL.md第一反应是打开 Notion新建一页标题写“邮件发送 Skill”然后分点列✅ 支持 HTML 正文✅ 可添加多个收件人✅ 自动插入签名档这本质上是在写产品 PRD不是在定义 Skill。真正的 Skill 设计起点必须是一份可执行的契约声明格式应严格对标编程语言中的函数签名。我们最终采用的规范是# send-email-v2 (stable) ## 触发语义 - 用户明确表达「发送邮件」意图且上下文含完整收件人、主题、正文三要素 - 或显式调用skill:send-email-v2 {to: ..., subject: ..., body: ...} ## 输入契约JSON Schema v2020-12 { type: object, required: [to, subject, body], properties: { to: { type: array, items: { type: string, format: email } }, subject: { type: string, maxLength: 120 }, body: { type: string, maxLength: 10000 }, cc: { type: array, items: { type: string, format: email }, default: [] } } } ## 输出形态严格 Markdown ## 邮件已发出 ✅ - **收件人**{to[0]}仅显示首位 - **主题**{subject} - **发送时间**{iso8601} - **追踪 ID**{trace_id} 提示本 Skill 不返回原始 SMTP 响应仅承诺业务层成功。网络层失败将自动重试 2 次间隔 1.5s第三次失败抛出 SKILL_EXEC_TIMEOUT。 ## ⚖️ 边界约束 - 单次调用最大附件数3 - 单附件体积上限8MB超出则拒绝并返回 ATTACHMENT_TOO_LARGE 错误码 - 不支持嵌套 HTML 表单或 JS 执行安全沙箱强制启用看到这里你可能想问为什么非得写 JSON Schema为什么输出必须是固定 Markdown 结构因为 Skill 的消费方不是人而是 Agent 的调度器Orchestrator、测试框架、监控系统、甚至另一个 Skill。举个真实例子我们曾用archify-skill归档知识库调用send-email-v2发送摘要。调度器需要根据send-email-v2的输出结构自动提取{trace_id}去查投递状态。如果输出是自由格式 Markdown比如有时写Tracking ID: xxx有时写ID: xxx甚至某次忘了写——整个自动化流水线就卡死。而强制约定## 邮件已发出 ✅开头 - **追踪 ID**{trace_id}格式让解析逻辑变成一行正则/- **追踪 ID**\s*(\w)/稳定度提升 92%。再看输入契约。不用自然语言描述“支持邮箱”而用format: email是因为我们的测试框架会自动生成 200 个边界用例空字符串、含空格的邮箱、adminlocalhost、test123.456.789.012……这些用例全部跑通才允许该 Skill 进入 staging 环境。这就是工程化和“写着玩”的分水岭。2.2 Skill 的生命周期管理从“写完即上线”到“版本可追溯、灰度可控制”很多团队把 Skill 当作文档写改完SKILL.md就git push然后在 Slack 里喊一句“邮件 Skill 更新啦”。结果第二天运营反馈“用户说发邮件没反应”一查发现是新版本把cc字段默认值从null改成了[]而上游workbuddy-skill的调用代码没适配传了{cc: null}导致后端 JSON 解析失败。Skill 必须有版本号、变更日志、兼容性声明。我们强制要求每个 Skill 文件名带-vN后缀如send-email-v2.md并在文件顶部声明## 兼容性矩阵 | 版本 | 输入兼容 | 输出兼容 | 破坏性变更 | |------|----------|----------|------------| | v1 | ✅ | ✅ | 无 | | v2 | ✅ | ❌ | 输出增加 ## 邮件已发出 ✅ 标题行移除原始响应体字段 | | v3 | ❌ | ✅ | to 字段改为必填数组v1/v2 支持单字符串 | ⚠️ 注意v2 → v3 为**主版本升级**需同步更新所有调用方并在灰度环境运行 48 小时。这个表格不是摆设。我们的 CI 流程会自动解析它并做三件事若检测到❌标记禁止合并到main分支除非 PR 中包含BREAKING_CHANGE.md说明文档若新增v3自动在测试框架中创建v2→v3兼容性测试集验证 v2 调用方能否无感过渡发布时自动将send-email-v2.md和send-email-v3.md同时部署通过路由规则控制流量比例如 95% 流量走 v25% 走 v3。这套机制让我们在三个月内平稳完成了 12 个核心 Skill 的主版本升级零线上事故。而之前没有版本管理时一次小改动平均引发 3.2 次跨服务故障。2.3 Skill 的可观测性不是“有没有日志”而是“能不能定位到具体哪一行契约被违反”很多团队说“我们有监控”点开 Grafana 却只见两条曲线skill_exec_total和skill_exec_error_rate。这毫无意义。当错误率突然飙升你根本不知道是哪个 Skill、哪个版本、哪条输入契约、在哪个环节崩的。真正的 Skill 可观测性必须下沉到契约层。我们在每个 Skill 执行入口注入统一拦截器自动记录input_hash: 对输入 JSON 做 SHA256脱敏后用于去重和归因contract_violation: 显式标记哪条契约被违反如input.to[0] formatemail failedoutput_schema_match: 输出是否匹配预设 Markdown 结构用 AST 解析器校验标题层级、列表格式、加粗语法boundary_hit: 是否触发边界如附件超限、重试达上限。这些字段直通 Loki配合如下 LogQL 查询5 秒内就能定位根因{jobskill-runtime} | json | __error__ | contract_violation ! | line_format {{.skill_name}} {{.input_hash}} {{.contract_violation}} | limit 20上周就靠这个查出mathematical-modeling-skill的一个隐藏 Bug它声称支持{method: linear_regression}但实际只校验了method字段存在没校验值是否在白名单[linear_regression, logistic_regression]内。结果用户传了method: ridge_regressionSkill 默默执行了默认分支输出却是## 模型训练完成 ✅—— 表面成功实则结果错误。这种“静默失败”比直接报错更危险而契约层日志让它无处遁形。3. 工程实现从 SKILL.md 到可执行、可测试、可编排的运行时3.1 Skill Runtime 的三层架构解析层、契约层、执行层很多人以为 Skill 就是写个 Python 函数再包个 API。错了。Skill Runtime 是一个独立服务必须解耦为三层否则无法支撑百级 Skill 的协同演进。解析层Parser Layer把 SKILL.md 编译成可执行中间表示我们不用 YAML 或 TOML坚持用 Markdown但做了关键增强支持!-- schema input --/!-- schema output --注释块内嵌 JSON Schema支持!-- boundary max_attachments3 --这类元指令输出结构化 IRIntermediate Representation{ name: send-email-v2, version: 2.0.0, trigger: { semantic: [send email], syntax: skill:send-email-v2 {to: \...\, ...} }, input_schema: { /* JSON Schema */ }, output_template: ## 邮件已发出 ✅\n- **收件人**{to[0]}\n..., boundaries: { max_attachments: 3, timeout_ms: 800 } }这个 IR 是所有后续流程的唯一数据源。测试框架读它生成用例调度器读它做参数校验监控系统读它做 Schema 匹配。Markdown 只是人类友好的编辑界面IR 才是机器可信的真相源。提示我们用markdown-it 自定义插件实现解析而非正则硬匹配。因为真实SKILL.md里常有代码块、引用块、表格正则会误伤。插件模式可精准定位注释节点稳定性实测 99.999%。契约层Contract Layer运行时强制执行契约不是“建议”这一层是 Skill 可靠性的基石。它在执行前、执行中、执行后三阶段介入Pre-execution: 用 AJV 库校验输入 JSON 是否符合input_schema不符合则立即返回结构化错误含field,error_code,suggestionDuring-execution: 启动资源沙箱CPU 限制 300m内存 512Mi并注入boundary_checker实时监控附件数量、网络请求次数Post-execution: 用remark-parse解析输出 Markdown校验是否含## 邮件已发出 ✅标题列表项是否含**追踪 ID**字段。所有检查失败都不走通用 HTTP 500而是返回 Skill 特定错误码如{ error: INPUT_VALIDATION_FAILED, detail: { field: to[0], reason: invalid email format, suggestion: 请检查邮箱地址是否含 符号 } }这种错误设计让前端能精准提示用户而不是弹个“操作失败请重试”。执行层Executor Layer隔离、可插拔、带 fallback 的执行引擎执行层不写死语言。我们支持三种 Executor类型适用场景示例shell简单 CLI 工具调用curl -X POST $EMAIL_API -d $INPUT_JSONpython需复杂逻辑或依赖库from emailer import send; send(**input)llm需 LLM 生成内容如写周报调用gpt-4-turboPrompt 模板来自output_template关键设计是fallback 机制。比如send-email-v2的pythonExecutor 失败时自动降级到shellExecutor 重试若仍失败则返回预置的 Markdown 错误模板## 邮件发送失败 ⚠️ - **原因**SMTP 服务暂时不可用 - **建议**请稍后重试或联系管理员检查 EMAIL_SMTP_HOST 配置 - **追踪 ID**{trace_id}这个模板本身也是 Skill 的一部分受同一套契约约束。它确保用户永远看到结构化反馈而不是裸露的 Python traceback。3.2 测试驱动的 Skill 开发不是“跑通就行”而是“契约全覆盖”我们禁用print()和手动测试。每个 Skill 必须配test/目录含三类用例happy_path.json: 符合所有契约的黄金路径输入boundary_cases/: 边界用例空数组、超长字符串、非法邮箱等compatibility/: 旧版本输入验证向后兼容性。测试框架自动执行加载SKILL.md→ 生成 IR对每个*.json用例用 AJV 校验输入是否匹配input_schema调用 Skill Runtime 执行解析输出 Markdown校验是否匹配output_template的 AST 结构检查日志中boundary_hit字段是否与预期一致。一个真实案例markdown-table-copy-skill声称支持“复制表格到剪贴板”但我们发现它在 Chrome 120 下因权限策略失败。测试框架在boundary_cases/chrome-120.json中模拟该环境执行后捕获到boundary_hit: clipboard_api_unavailable自动标记该用例为skip_on_chrome_120并通知前端加降级提示。这种测试不是为了“证明它能跑”而是为了“证明它在哪种条件下不能跑”。注意所有测试用例的输入 JSON 必须通过input_schema校验否则测试直接失败。这倒逼团队在写 Skill 时就必须想清楚边界——你不可能用一个模糊的“支持各种邮箱”描述来生成可验证的测试用例。3.3 Skill 编排不是“顺序调用”而是“基于契约的状态机”很多团队用agent.md描述流程“先查天气再订会议室最后发邮件”。这仍是人话。真正的编排必须基于 Skill 的输出契约构建状态机。我们用skill-flow.yaml定义name: meeting-arranger steps: - name: get-weather-v1 on_success: check-room-v1 on_failure: notify-failure-v1 - name: check-room-v1 # 输入自动从上一步输出提取weather_data get-weather-v1.output.body input_map: location: get-weather-v1.output.body.location date: today on_success: send-email-v2 - name: send-email-v2 input_map: to: [teamcompany.com] subject: 会议安排确认 - {{check-room-v1.output.body.room_name}} body: 天气{{get-weather-v1.output.body.summary}}\n会议室{{check-room-v1.output.body.room_name}}关键点在于input_map它用 Jinja2 语法从上游 Skill 的结构化输出中提取字段。而这个提取能力完全依赖于get-weather-v1.md里明确定义的输出模板## 天气预报 ✅ - **城市**{location} - **日期**{date} - **摘要**{summary} - **温度**{temp_c}°C没有这个契约input_map就是空中楼阁。我们曾因此重构过整套编排引擎——把原来基于正则提取的output.body.match(/摘要(.*)/)全部替换为 AST 节点路径查询output.ast.find(list_item).find(strong[text摘要]).next_sibling.text。虽然开发成本高但稳定性从 83% 提升到 99.4%且支持任意 Markdown 变体哪怕用户把摘要写成【摘要】AST 层也能归一化处理。4. 避坑指南那些在SKILL.md里埋得最深、炸得最狠的 7 个雷4.1 雷区一把“用户能说什么”当 Skill 触发条件而不定义语义边界现象taste-skill.md里写“支持用户说‘给我来点好吃的’‘饿了’‘馋了’”结果上线后用户说“我饿了想辞职”Skill 也触发并开始推荐餐厅。根因触发条件未做语义过滤。“饿了”在职场语境下是离职信号在生活语境下才是餐饮需求。解法触发条件必须绑定上下文约束。我们要求trigger.semantic字段必须含context子字段## 触发语义 - 语义[food_recommendation] - 上下文约束 - 当前对话主题为 #lifestyle 或 #food 频道 - 或用户最近 3 条消息含 美食|餐厅|吃|饿|馋 且无 辞职|离职|HR|合同 等对抗词我们用轻量级 RNN 模型仅 12KB做实时上下文打分阈值 0.7 才触发。模型训练数据来自内部 2000 条真实对话标注不依赖大模型延迟 15ms。4.2 雷区二输出 Markdown 时滥用自由格式导致下游解析崩溃现象markdown-formula-skill.md输出Emc²但有时用$Emc^2$有时用$$Emc^2$$有时甚至手写sup2/sup。下游markdown-to-word-workflow工具直接报错。根因未定义数学公式渲染标准。不同 Markdown 解析器对$符号处理不一致。解法在output_template中强制约定公式语法并用remark-math插件统一转译## 计算结果 ✅ - **质能方程**$E mc^2$LaTeX inline style - **推导过程** $$E_k \\frac{1}{2}mv^2$$ LaTeX display style双美元符CI 流程会用remark-cli验证所有$和$$是否成对、是否含非法字符。不通过则阻断发布。4.3 雷区三Skill 间循环依赖形成“死亡螺旋”现象ponytail-skill.md美颜修图调用ai-agent-verilog-code-skill.md生成硬件描述来优化图像处理算法而后者又调用ponytail-skill.md渲染生成的 Verilog 代码效果图……最终 OOM。根因未定义 Skill 调用深度限制和依赖图谱。解法Runtime 层强制注入call_depthheader初始为1每跳加1超过5则拒绝执行并返回CALL_DEPTH_EXCEEDED。同时我们用dep-graph工具扫描所有SKILL.md中的skill:引用生成可视化依赖图每周邮件告警环形依赖。4.4 雷区四忽略本地化i18n契约导致多语言环境失效现象humanizer-skill.md人性化润色在英文环境下输出## Rewritten ✅中文环境却还是## Rewritten ✅前端无法切换语言。根因输出模板未参数化语言变量。解法output_template支持{{i18n.header.success}}语法底层对接 i18n JSON 包// i18n/zh.json { header: { success: 润色完成 ✅ } } // i18n/en.json { header: { success: Rewritten ✅ } }且要求所有i18nkey 必须在SKILL.md的i18n_keys字段中声明否则 CI 报错。这保证了翻译完整性。4.5 雷区五把 Skill 当作“胶水层”硬塞业务逻辑现象coze-skill.md对接 Coze 平台里写了 200 行 Python处理 OAuth2 流程、Webhook 签名、消息加解密……结果每次 Coze API 升级都要重写整个 Skill。根因Skill 职责越界。它应该只负责“协议转换”不负责“业务实现”。解法拆分为coze-protocol-skill纯协议层只做 JSON 映射 coze-business-skill调用前者。前者契约稳定Coze API 协议极少变后者可高频迭代。我们用harness框架轻量级适配器封装协议层agent框架业务编排层调用它——这才是harness 和 agent 区别的真实工程含义。4.6 雷区六未定义 Skill 的“冷启动”行为导致首次调用失败现象hermes-agent-skill.md语音转文字首次调用时因模型加载耗时 3.2s超时被杀用户看到空白反馈。根因未声明初始化依赖和预热机制。解法在SKILL.md中增加initialization区块## ⚙️ 初始化要求 - 首次加载需下载 whisper-large-v3.bin1.2GB - 预热命令curl -X POST /skill/hermes-agent-v1/warmup - 预热耗时≤ 8sSLO - 预热失败时返回 INITIALIZATION_PENDING前端轮询至就绪K8s Init Container 在 Pod 启动时自动执行预热成功率 99.99%。4.7 雷区七用skill recorder录制用户操作生成 Skill却忽略语义抽象现象skill-recorder录下用户点击“导出为 Word”生成 Skill 脚本但该脚本硬编码了当前文档 IDdoc_abc123换一篇文档就失效。根因录制工具未做语义升维停留在像素级操作。解法skill recorder输出不是最终 Skill而是recording.json需经人工审核升维// recording.json原始 { actions: [ {type: click, selector: #export-btn}, {type: select, value: word} ], context: {document_id: doc_abc123} }升维后SKILL.md## 触发语义 - 用户说“导出当前文档为 Word” 或 点击导出按钮且当前有活跃文档 ## 输入契约 { type: object, required: [document_id], properties: { document_id: { type: string, pattern: ^doc_[a-z0-9]{6}$ } } }我们规定所有录制生成的 Skill必须经过abstraction-review流程由 Senior Engineer 签字才能上线。这步看似慢实则避免了 80% 的“录完即废” Skill。5. 实战复盘从零搭建一个可交付的research-skill科研文献助手现在我们用一个完整案例把前述所有原则串起来。目标做一个research-skill能根据用户研究方向推荐 3 篇顶会论文并生成带引用格式的 Markdown 摘要。5.1 第一步定义不可妥协的契约15 分钟我们坐下来用白板写下必须回答的五个问题问题答案依据触发条件用户说“推荐相关论文”“找顶会文献”“给我几篇 CVPR 最新工作”且上下文含技术关键词如“transformer”“diffusion”避免泛化触发参考 ACL 2023 用户调研报告输入契约{topic: string, conference: [CVPR,ICML,NeurIPS], year: 2023|2024}topic必须含至少 2 个技术词防止“AI”这种宽泛词导致噪声结果输出形态严格 Markdown## 推荐论文 ✅### [1] 论文标题- **作者**XXX- **会议**CVPR 2024- **摘要**XXX- **引用**XXX确保下游markdown-to-word-workflow可解析失败边界Google Scholar API 调用超时 3s或返回论文数 2或topic未命中学术词典我们维护的 12000 术语库SLO 要求 95% 请求 2.5s演化路径v1 支持单 topicv2 支持[topic1, topic2]数组v2 输入兼容 v1自动 wrap为未来多模态检索留接口5.2 第二步编写research-skill-v1.md30 分钟# research-skill-v1 (stable) ## 触发语义 - 用户明确表达「推荐论文」「查找文献」意图且消息含至少两个技术关键词如 transformer, diffusion, LLM - 或显式调用skill:research-skill-v1 {topic: transformer, conference: CVPR, year: 2024} ## 输入契约JSON Schema v2020-12 { type: object, required: [topic, conference, year], properties: { topic: { type: string, minLength: 3, description: 研究主题需含至少两个学术术语用空格分隔 }, conference: { type: string, enum: [CVPR, ICML, NeurIPS, ACL, EMNLP] }, year: { type: string, enum: [2023, 2024] } } } ## 输出形态严格 Markdown ## 推荐论文 ✅ ### [1] {paper1.title} - **作者**{paper1.authors} - **会议**{paper1.conference} - **摘要**{paper1.abstract} - **引用**{paper1.citation} ### [2] {paper2.title} - **作者**{paper2.authors} - **会议**{paper2.conference} - **摘要**{paper2.abstract} - **引用**{paper2.citation} ### [3] {paper3.title} - **作者**{paper3.authors} - **会议**{paper3.conference} - **摘要**{paper3.abstract} - **引用**{paper3.citation} 提示本 Skill 从 Google Scholar API 获取数据结果按相关性排序。若未找到足够论文将返回 NO_SUFFICIENT_PAPERS 错误。 ## ⚖️ 边界约束 - 单次请求最多返回 3 篇论文 - Google Scholar API 调用超时3000ms - topic 字符串必须匹配内部学术词典12000 术语否则返回 TOPIC_NOT_RECOGNIZED ## 兼容性矩阵 | 版本 | 输入兼容 | 输出兼容 | 破坏性变更 | |------|----------|----------|------------| | v1 | ✅ | ✅ | 无 |5.3 第三步实现与测试2 小时Executor选python用scholarly库但重写其search_pubs方法加入我们自己的术语匹配和去重逻辑Boundary Checker监控 API 调用耗时超 3s 自动中断并返回错误Output Validator用markdown-it解析输出校验必须含## 推荐论文 ✅且恰好有 3 个###标题Test Caseshappy_path.json:{topic: vision transformer, conference: CVPR, year: 2024}boundary_cases/topic-too-broad.json:{topic: AI, ...}boundary_cases/api-timeout.json: Mock 返回 3200ms 延迟全部通过后CI 自动部署到 staging。5.4 第四步灰度发布与监控持续发布时设置 5% 流量到 v195% 到 mock Skill返回固定假数据监控research-skill-v1.input_schema_valid_rate目标 ≥99.5%发现topic-too-broad用例占 12%立即在前端加输入提示“请用具体技术词如‘diffusion model’而非‘AI’”48 小时后错误率降至 0.3%全量发布。上线一周后research-skill成为内部使用率最高的 Skill日均调用 1200

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价