实际落地中,你遇到过 Skill 设计的哪些反模式?如何避免?
“踩过的坑太多了,我挑五个最典型的反模式说,每个都是真金白银换来的教训。
🪤 反模式一:上帝 Skill
症状是把一个 Skill 做得太全能。比如一个‘智能助手’ Skill,既能查天气、又能订机票、还能写代码、翻译文档。元信息里的 trigger_examples 写了上百条,什么都能触发它。
这会导致三个问题:
-
路由混淆:Agent 很难区分该用它还是用专业 Skill,经常选错。
-
维护灾难:改一个功能可能牵一发动全身,版本管理失控。
-
审核困难:评审者看不清这个 Skill 到底操作了哪些权限。
✅ 怎么避免:坚持单一职责原则。一个 Skill 只做一个领域的事。如果发现 trigger_examples 超过 10 条还覆盖了不同领域,就该拆了。我们内部有条铁律:如果你不能用一句话说清这个 Skill 做什么,它就太大了。
🪤 反模式二:空壳 Skill
和上帝 Skill 相反,这个 Skill 几乎没有自己的逻辑,只是简单包了一层 API。比如一个‘查城市’ Skill,内部就调了一下第三方城市 API,没有任何参数校验、降级逻辑、缓存策略。
危害:
-
浪费编排资源:Agent 要多编排一个节点,增加延迟。
-
增加维护负担:多一个 Skill 就要维护它的元信息、版本、监控。
-
掩盖真实能力:用户以为 Agent 有智能,其实只是个透传。
✅ 怎么避免:Skill 必须有不可替代的增值逻辑。如果只是透传 API,不如直接在 Agent 的工具层注册为一个简单工具,不要包装成 Skill。判断标准:去掉这个 Skill,业务流程还能不能轻松实现?能的话,它就不值得作为一个独立 Skill。
🪤 反模式三:硬编码依赖
Skill A 内部直接实例化 Skill B,或者写死调用 Skill C 的接口。最常见的是‘下单 Skill’内部直接调用‘支付 Skill’,写死 URL 和参数名。
危害:
-
复用性归零:支付逻辑换了,所有调用它的 Skill 都得改。
-
测试困难:想单独测下单,必须启动支付服务。
-
无法灰度:不能对支付 Skill 做 A/B 测试或蓝绿部署。
✅ 怎么避免:Skill 之间只能通过上下文声明式依赖,不能直接调用。Skill A 只声明“我需要一个能支付的 Skill 的输出”,由编排引擎注入。这样支付 Skill 换成任何版本、任何供应商,下单 Skill 完全无感。这也是 Agent 架构和传统微服务最大的区别——编排是中心化的,能力是去中心化的。
🪤 反模式四:静默失败
Skill 出错了不报,或者只打一行 log 然后返回 null。常见于 try-catch 包住整个方法,catch 里什么都不做。
危害:
-
故障不可见:用户看到“未查询到结果”,以为是正常无数据,其实是下游挂了。
-
排查无门:线上出了问题,日志里干干净净,只能靠用户投诉才发现。
-
雪崩推手:错误被吞,上游以为正常,继续加压,最终拖垮整个链。
✅ 怎么避免:Skill 必须显式失败。所有异常分类处理:
-
可恢复的(超时、限流)→ 重试并暴露为降级状态。
-
不可恢复的(权限不足、数据非法)→ 立即失败并返回结构化错误,包含错误码和用户可读的提示。
-
任何被 catch 的异常必须写入审计日志,并上报监控告警。内部规范是:只有明确知道如何恢复的异常才允许 catch,否则必须向上抛。
🪤 反模式五:上下文污染
Skill 把自己的中间状态或敏感数据写到了不该写的上下文区域。比如一个‘查用户信息’ Skill,把用户手机号明文写到了全局上下文,然后被‘发送邮件’ Skill 读走,还写进了日志。
危害:
-
安全隐患:敏感数据扩散,越权泄露。
-
键冲突:两个 Skill 都用了
result这个键,后面的覆盖前面的。 -
上下文膨胀:大量无用中间数据塞满上下文,超出 LLM 窗口限制。
✅ 怎么避免:
-
命名空间隔离:每个 Skill 只能写自己的命名空间,如
skill_outputs.user_query.phone。 -
元信息显式声明输出键:没声明的键写不进去。
-
数据最小化:只写下游真正需要的字段,中间过程数据用完即丢。
-
定期审查上下文大小,超过阈值就触发压缩。